C++ 阻塞套接字返回 EAGAIN

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/735249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 17:01:26  来源:igfitidea点击:

Blocking socket returns EAGAIN

c++clinuxsockets

提问by Evan Teran

One of my projects on Linux uses blocking sockets. Things happen very serially so non-blocking would just make things more complicated. Anyway, I am finding that often a recv()call is returning -1with errnoset to EAGAIN.

我在 Linux 上的一个项目使用阻塞套接字。事情是连续发生的,所以非阻塞只会让事情变得更复杂。无论如何,我发现经常一个recv()调用返回-1errno设置为EAGAIN

The manpage only really mentions this happening for non-blocking sockets, which makes sense. With non-blocking, the socket may or may not be available so you might need to try again.

man页面只真正提到了非阻塞套接字发生的这种情况,这是有道理的。对于非阻塞,套接字可能可用也可能不可用,因此您可能需要重试。

What would cause it to happen for a blocking socket? Can I do anything to avoid it?

什么会导致阻塞套接字发生?我能做些什么来避免它吗?

At the moment, my code to deal with it looks something like this (I have it throw an exception on error, but beyond that it is a very simple wrapper around recv()):

目前,我处理它的代码看起来像这样(我让它在错误时抛出异常,但除此之外它是一个非常简单的包装器recv()):

int ret;
do {
    ret = ::recv(socket, buf, len, flags | MSG_NOSIGNAL);
} while(ret == -1 && errno == EAGAIN);


if(ret == -1) {
    throw socket_error(strerror(errno));
}
return ret;

Is this even correct?The EAGAINcondition gets hit pretty often.

这甚至正确吗?这种EAGAIN情况经常受到打击。

EDIT:some things which I've noticed which may be relevant.

编辑:我注意到的一些可能相关的事情。

  1. I do set a read timeout on the socket using setsockopts(), but it is set to 30 seconds. the EAGAIN's happen way more often than once every 30 secs. CORRECTIONmy debugging was flawed, EAGAIN's don't happen as often as I thought they did. Perhaps it is the timeout triggering.

  2. For connecting, I want to be able to have connect timeout, so I temporarily set the socket to non-blocking. That code looks like this:

    int      error = 0;
    fd_set   rset;
    fd_set   wset;
    int      n;
    const SOCKET sock = m_Socket;
    
    // set the socket as nonblocking IO
    const int flags = fcntl (sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
    
    errno = 0;
    
    // we connect, but it will return soon
    n = ::connect(sock, addr, size_addr);
    
    if(n < 0) { 
        if (errno != EINPROGRESS) {
            return -1;
        }
    } else if (n == 0) {
        goto done;
    }
    
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(sock, &rset);
    FD_SET(sock, &wset);
    
    struct timeval tval;
    tval.tv_sec = timeout;
    tval.tv_usec = 0;
    
    // We "select()" until connect() returns its result or timeout
    n = select(sock + 1, &rset, &wset, 0, timeout ? &tval : 0);
    if(n == 0) {    
        errno = ETIMEDOUT;
        return -1;
    }
    
    if (FD_ISSET(sock, &rset) || FD_ISSET(sock, &wset)) {
        socklen_t len = sizeof(error);
        if (getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
            return -1;
        }
    } else {
        return -1;
    }
    
    done:
    // We change the socket options back to blocking IO
    if (fcntl(sock, F_SETFL, flags) == -1) {
        return -1;
    }
    return 0;
    
  1. 我确实使用 对套接字设置了读取超时setsockopts(),但它设置为 30 秒。的EAGAIN的一次,每30秒的方式更经常发生。更正我的调试有缺陷,EAGAIN不会像我想象的那样经常发生。也许是超时触发。

  2. 对于连接,我希望能够有连接超时,所以我暂时将套接字设置为非阻塞。该代码如下所示:

    int      error = 0;
    fd_set   rset;
    fd_set   wset;
    int      n;
    const SOCKET sock = m_Socket;
    
    // set the socket as nonblocking IO
    const int flags = fcntl (sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
    
    errno = 0;
    
    // we connect, but it will return soon
    n = ::connect(sock, addr, size_addr);
    
    if(n < 0) { 
        if (errno != EINPROGRESS) {
            return -1;
        }
    } else if (n == 0) {
        goto done;
    }
    
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(sock, &rset);
    FD_SET(sock, &wset);
    
    struct timeval tval;
    tval.tv_sec = timeout;
    tval.tv_usec = 0;
    
    // We "select()" until connect() returns its result or timeout
    n = select(sock + 1, &rset, &wset, 0, timeout ? &tval : 0);
    if(n == 0) {    
        errno = ETIMEDOUT;
        return -1;
    }
    
    if (FD_ISSET(sock, &rset) || FD_ISSET(sock, &wset)) {
        socklen_t len = sizeof(error);
        if (getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
            return -1;
        }
    } else {
        return -1;
    }
    
    done:
    // We change the socket options back to blocking IO
    if (fcntl(sock, F_SETFL, flags) == -1) {
        return -1;
    }
    return 0;
    

The idea is that I set it to non-blocking, attempt a connect and select on the socket so I can enforce a timeout. Both the set and restore fcntl()calls return successfully, so the socket should end up in blocking mode again when this function completes.

这个想法是我将它设置为非阻塞,尝试在套接字上进行连接和选择,以便我可以强制超时。set 和 restorefcntl()调用都成功返回,所以当这个函数完成时,套接字应该再次进入阻塞模式。

回答by Hasturkun

It's possible that you have a nonzero receive timeout set on the socket (via setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,...)) as that would also cause recv to return EAGAIN

您可能在套接字上设置了非零接收超时(via setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,...)),因为这也会导致 recv 返回EAGAIN

回答by Rick C. Petty

Is it possible that you're using MSG_DONTWAITis being specified as part of your flags? The manpage says EAGAINwill occur if no data is available and this flag is specified.

是否有可能将您使用MSG_DONTWAIT的指定为标志的一部分?如果没有数据可用并且指定了此标志,则该man页面EAGAIN将显示。

If you really want to force a block until the recv()is somewhat successful, you may wish to use the MSG_WAITALLflag.

如果你真的想强制阻止直到recv()有点成功,你可能希望使用MSG_WAITALL标志。

回答by rmeador

I don't suggest this as a first-attempt fix, but if you're all out of options, you can always select()on the socket with a reasonably long timeout to force it to wait for data.

我不建议将其作为第一次尝试修复,但是如果您没有任何选择,您可以始终select()在套接字上使用相当长的超时来强制它等待数据。

回答by Edward Alberts

EAGAINis generated by the OS almost like an "Oops! I'm sorry for disturbing you.". In case of this error, you may try reading again, This is not a serious or fatal error. I have seen these interrupts occur in Linux and LynxOS anywhere from one a day to 100 times a day.

EAGAIN是由操作系统生成的,几乎就像“哎呀!对不起,打扰你了。”。如果出现此错误,您可以再次尝试阅读,这不是严重或致命的错误。我已经看到这些中断在 Linux 和 LynxOS 中发生,从一天一次到一天 100 次不等。