阻塞套接字返回 EAGAIN

7
我的Linux项目之一使用阻塞套接字。事情发生得非常串行,因此非阻塞只会让事情更加复杂。无论如何,我发现经常会出现recv()调用返回-1,并且errno设置为EAGAINman页面只提到这种情况发生在非阻塞套接字上,这是有道理的。对于非阻塞套接字,套接字可能可用,也可能不可用,因此您可能需要再试一次。
什么会导致阻塞套接字发生这种情况?我能做些什么来避免它发生?
目前,处理它的代码看起来像这样(我在错误时抛出异常,但除此之外,它只是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;

这是否正确? EAGAIN 条件经常被触发。

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

  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. CORRECTION my 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;
    

我的想法是将其设置为非阻塞状态,尝试连接并在套接字上进行选择以便我可以强制执行超时。无论是设置还是恢复fcntl()调用都返回成功,因此当此函数完成时,套接字应该再次处于阻塞模式。

4个回答

20

可能你在套接字上设置了非零的接收超时时间(通过setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,...)),这也会导致recv返回EAGAIN


是的,但它被设置为30000毫秒,我比那更经常收到EAGAIN错误。几乎一直如此。 - Evan Teran
更正 我的调试有误,EAGAIN并不像我想象的那样经常发生。也许是超时触发了。 - Evan Teran
如果有人需要参考,请查看此处(http://man7.org/linux/man-pages/man7/socket.7.html),并搜索“SO_RCVTIMEO”。 - yixiang

1

你是否在使用MSG_DONTWAIT作为标志的一部分?根据man页面,如果指定了此标志且没有可用数据,则会出现EAGAIN

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


我刚刚在我的源代码树中使用了grep命令,但没有使用MSG_DONTWAIT。 - Evan Teran

0

EAGAIN 几乎就像是操作系统发出的一个“哎呀!对不起打扰你了。”的信号。在遇到这个错误时,你可以尝试重新读取。这不是一个严重或致命的错误。我曾经见过这些中断在 Linux 和 LynxOS 中每天出现一次到一百次。


0

我不建议这作为第一次尝试的修复方法,但如果你已经没有其他选择,你可以在套接字上使用select()并设置一个相当长的超时时间来强制等待数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接