【问题标题】:non-blocking recv returns 0 when disconnected [closed]断开连接时非阻塞recv返回0 [关闭]
【发布时间】:2013-07-24 03:47:21
【问题描述】:

当断开连接发生时,我正在尝试“捕捉”。 但我实际上不明白出了什么问题。 recv() 返回 0,errno 设置为 0,ioctl 返回 0。我在网上搜索了 6 个小时,但没有成功。谁能告诉我怎么了?

问候。

bool Network::setBlocking(bool blocking)
{
    // sets blocking or non-blocking mode.

    int flags = blocking ? 1 : 0; 
    return ioctl(this->sockfd, FIONBIO, &flags) ? false : true;
}

NetworkStatus Network::status()
{
    // returns socket status.

    struct timeval tv;
    fd_set  fd;
    int result = 0;

    tv.tv_sec  = 3;
    tv.tv_usec = 0;

    FD_ZERO(&fd);
    FD_SET(this->sockfd, &fd);

    result = select(this->sockfd + 1, &fd, 0, 0, &tv);

    if(result == -1)
    {
        return NETWORK_ERROR;
    }
    else if(result)
    {
        return NETWORK_READYREAD;
    }
    else
    {
        return NETWORK_TIMEOUT;
    }
}

int Network::bytesAvailable()
{
    // returns number of bytes available.

    int bytes = 0;

    if(ioctl(this->sockfd, FIONREAD, &bytes) < 0 || errno)
    {
        return -1;
    }

    return bytes;
}

int Network::read(char *buffer, int size)
{
    // reads data from socket.

    return recv(this->sockfd, buffer, size, 0);
}
...
int main(int argc, char* argv[])
{
    Network net;
    ...
    while(true)
    {
        switch(net.status())
        {
            ...
            case NETWORK_READYREAD:
            {
                int bytesAvailable = net.bytesAvailable();
                char temp[bytesAvailable];
                int len = net.read(temp, bytesAvailable);
                printf("len: %d\nerrno: %d\nbytesAvailable: %d\n", len, errno, bytesAvailable); // len: 0, errno: 0, bytesAvailable: 0
                break;
            }
        }
    } // status
    return 0;
}

【问题讨论】:

  • 是的,我不明白问题出在哪里。你到底在期待什么?
  • 我期望返回-1。因为当套接字关闭时,它应该在错误时返回 -1。非阻塞套接字可以接收 0 字节,这意味着还没有可用的数据,我不太明白我应该检查什么。
  • 不,当没有数据时你会得到一个“错误”,当有断开连接时你会得到 0。不要认为“错误”意味着错误。接受它并继续前进:) 你得到了你应该得到的——这很奇怪,但它是正确的。
  • 我不明白-.-

标签: c++ c sockets


【解决方案1】:

recv() 返回零对等端断开连接。你没有问题。这是预期的,正确的行为。

【讨论】:

  • 我要补充一点,当对等方优雅地断开连接时,recv() 返回零。如果发生网络错误(例如对等方崩溃或失去网络连接),recv() 可能会返回 -1。
  • 我用词很谨慎。这些不是对等方断开连接,它们是网络错误,通常导致本地主机由于 it 断开连接而发出ECONNRESET
  • 不,它有时会在不关闭套接字的情况下返回 0,这意味着还没有可用的数据。我说,我使用的是非阻塞套接字。
  • @selbie:应该注意,这通常不会发生,或者它只会在相当长的延迟后发生,如果有的话。只要没有发送任何内容,就不会检测到由于各种各样的“网络错误”而导致的连接丢失,并且在接收端根本不会检测到。检测断开链接的唯一方法是发送并返回错误。另一方面,recv 除了阻止或返回EWOULDBLOCK 之外,不会做任何事情,如果什么都没有。 Keepalive 探测将是一个例外,但它们发送的频率非常低(2 小时),以至于它们真的不算数。
  • @selbie:没错,但对我来说,您的第一条评论听起来像是recv 可以检测到网络错误(并通过返回-1 来报告它们),但事实并非如此。当没有可用的东西时,非阻塞套接字总是 生成EWOULDBLOCK(或EAGAIN)(而阻塞的套接字只是......好吧,阻塞)。这当然包括网络错误,但也包括另一端根本没有发送任何信息,或者发送的数据包仍然“在线”或 TCP 堆栈中的某个位置。
【解决方案2】:

当对方关闭socket或者做shutdown(Send)时,在你这边select会返回socket可读,所以select命令会返回1表示有数据要读取(其实没有数据,它只是一个表明套接字已关闭的信号),但ioctl 返回0 作为可用字节,recv() 也返回0,这正是 TCP 的工作原理。所以在你的程序中你应该注意bytesAvaliable返回0和/或read()返回0。在这些情况下,套接字已关闭,您也应该关闭您的一侧并等待(接受)或建立(连接)新连接。

编辑:添加评论:

嗯,不完全是。当recv() = 0 时,套接字关闭或关闭以进行发送,当= -1 时,套接字中有错误。如果套接字是非阻塞的(设置了O_NONBLOCK),您应该检查错误(unix 上的errno,Windows 上的WSAGetLastError())和EWOULDBLOCKEAGAIN 表示没有可用数据并且套接字仍然有效。此外,在极少数情况下,recv() 可能会返回-1 和错误EINTR,这意味着在recv() 操作期间收到了中断(信号),在这种情况下您应该忽略错误(除非您正在等待此类信号)并重新发出recv()

ret=recv(socket, buff, length, 0);
if (ret > 0) 
{
    // data has been received
}
else if (ret == 0) 
{
    // socket has been closed or shutdown for send
}
else {
   // there is an error, let's see what it is
   int error = errno; // or WSAGetLastError()
   switch (error)
   {
         case EAGAIN:
         case EWOULDBLOCK: 
              // Socket is O_NONBLOCK and there is no data available
              break;
         case EINTR: 
              // an interrupt (signal) has been catched
              // should be ingore in most cases
              break;
         default:
              // socket has an error, no valid anymore
              return;
   }
}

话虽如此,您必须清楚的一件事是,如果recv() 返回0-1,则套接字不再有效,应该关闭,但同样取决于许多因素,您使用的协议保持套接字之间的对话,如果它处于非阻塞状态等。例如,对方可以发出shutdown();在这种情况下,您的recv() 将返回0,但您仍然可以通过该套接字执行send() 而不会出错。

【讨论】:

  • 那么,如果recv()
  • @user2399415 查看我编辑的答案以获得解释。
  • 感谢您的回复,但有趣的是,日志向我显示:收到 1 个字节,收到 5 个字节,收到 0 个字节,收到 0 个字节,...,收到 1 个字节,收到 1 个字节,完成接收。为什么?编辑这些数字是 recv() 调用的返回值。
  • 尝试读取整个缓冲区、sizeof(buffer) 或您使用 malloc() 分配的大小,而不是读取 bytesAvailable。当确实有更多字节进入时,可能是 select 执行太快并且 ioctl 返回 1, 2 ,5。他们将您的问题置于 [hold] 中。您必须提出具体问题并尽量避免将其用作聊天室。
  • 谢谢,目前看来工作正常。
猜你喜欢
  • 2021-03-08
  • 2020-12-11
  • 2011-09-13
  • 1970-01-01
  • 2012-09-21
  • 1970-01-01
  • 2010-12-30
  • 2015-07-20
  • 1970-01-01
相关资源
最近更新 更多