【问题标题】:Can a C socket recv 0 bytes without the client shutting the connection?C 套接字可以在没有客户端关闭连接的情况下接收 0 个字节吗?
【发布时间】:2016-10-27 13:27:49
【问题描述】:

我已经用 C 语言实现了一个 Web 服务器。它在已连接的阻塞套接字上调用 recv() 以接收传入的 HTTP 请求。 Linux 手册页对阻塞套接字上的 recv() 进行了以下说明:

如果套接字上没有可用的消息,则接收调用会等待消息到达...接收调用通常会返回任何可用数据,直到请求的数量,而不是等待接收请求的全部数量。

...

这些调用返回接收到的字节数,如果发生错误,则返回 -1...当对等方执行有序关闭时,返回值为 0。

因此,为了接收完整的请求,我的服务器代码包含以下形式的循环:

int connfd; // accepted connection
int len;    // # of received bytes on each recv call

...

while (/* HTTP message received so far has not terminated */) {
  len = recv(connfd, ...);
  if (len == 0) {
    // close connfd, then escape loop
  } else if (len < 0) {
    // handle error
  } else {
    // process received bytes
  }
}    
  

我的问题:recv() 是否有可能由于网络问题而返回 0 字节并且客户端没有执行有序关闭,从而导致我的代码过早退出循环?手册页含糊不清。

【问题讨论】:

  • 这是一个“可以”还是“我该怎么做”的问题?也就是说,您是希望能够接收零字节还是只想知道它是否会发生?
  • @Clifford 只是一个“可以”的问题
  • 好的。尽管接受和投票赞成的答案强调“否”,但答案显然是“是”,尽管在标题和正文之间,您的问题似乎将 receiving 零字节与 返回值为零;它们并不完全相同。也就是返回值>0时只代表接收到的字节数。
  • @Clifford 你说得对,我的问题措辞含糊不清。我很抱歉。更准确地说,我的问题是 recv() 是否有可能返回值 0 以指示它已从客户端接收到 0 个字节但客户端没有执行关闭。正如我在对约翰回答的评论中解释的那样,造成这种混乱的原因是对“消息”概念的误解。
  • 对于我更精确措辞的问题,观察到的行为只是返回 value 0,我相信 John 的答案是正确的。

标签: c sockets server recv


【解决方案1】:

TL;DR:POSIX 说“不”。

我认为手册页不是那么不清楚,但POSIX's description 可能更清楚一点:

recv() 函数应从连接模式或无连接模式套接字接收消息。

[...]

成功完成后,recv() 将返回消息的长度(以字节为单位)。如果没有消息可以接收并且对端已经执行了有序的关闭,recv() 应该返回 0。否则,应该返回 -1 并设置errno 来指示错误。

因此,POSIX 允许使用三种替代方案:

  • recv() 成功接收消息并返回其长度。根据定义,长度是非零的,因为如果没有收到任何字节,则没有收到任何消息。 recv() 因此返回一个大于 0 的值。

  • 没有收到来自对等方的消息,我们相信不会收到任何消息,因为对等方(在recv() 返回时)已按顺序关闭了连接。 recv() 返回 0。

  • “否则”发生了其他事情。 recv() 返回 -1。

在任何情况下,recv() 将阻塞如果没有数据可用并且套接字上没有可用的错误条件,除非套接字处于非阻塞模式。在非阻塞模式下,如果调用recv()时既没有数据也没有错误条件,则属于“否则”的情况。

不能完全排除给定系统不符合 POSIX 的可能性,但您必须以某种方式决定如何解释函数结果。如果您在声称符合 POSIX(至少就该函数而言)的系统上调用 POSIX 定义的函数,那么很难与依赖 POSIX 语义争论。

【讨论】:

  • 问题可能是是否可以接收零字节,而不是recv() 是否可以返回 0 作为返回值。
  • @Clifford,也许是这样,如果确实如此,那么这个答案解决了它说“如果没有收到任何字节,那么没有收到任何消息”。
  • 是的,但是如果有的话,文档中缺乏明确性是recv() 没有提到超时的可能性。第一行说“POSIX 说“不””,这是一个强调的答案,但在对问题的最严格解释中是不正确的。如果不是因为第一行,我不会发表评论。答案实际上是在“发生了其他事情”中,这几乎没有什么可以澄清的。
  • SOCK_DGRAMSOCK_SEQPACKET 套接字的情况下,第一点是不正确的,它们可以有0 长度的消息。但是,似乎 OP 隐含地询问SOCK_STREAM,因为这通常用于传输 HTTP
  • 啊,我现在明白我的困惑了。我误解了“消息”的概念,认为手册页指的是整个 HTTP 请求。我认为recv() 只会阻塞,直到它开始接收 HTTP 请求的开始,但可以在任何后续的recv() 调用中立即返回(可能在接收到的数据的 0 字节上)。但这没有意义,因为此时接收到的数据只是一个未解释的字节流。谢谢大家。
【解决方案2】:

如果使用setsockopt() 设置了超时并且超时到期并且没有接收到数据,则recv() 将返回-1,不会缓冲任何数据,并且errno 将设置为EAGAIN 或@ 987654326@(这些可能具有相同的值)。至关重要的是,套接字将保持打开状态。

例如:

struct timeval tv = {1,0}; // one second timeout
setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) ) ;

int count = recv( sockfd, buf, sizeof(buf), 0 ) ;
if( count < 0 (errno == EAGAIN || errno == EWOULDBLOCK) )
{
    count = 0 ;
}

// If count is still < 0, then error, else there is `count` data
// in `buf`, where `count` may be zero.

概括此功能以供重用可能很有用:

int timeout_recv( int socket, 
                  void *buffer, size_t length, 
                  int flags, int tmo_millisec )
{
    struct timeval tv = {0};
    tv.tv_sec = tmo_millisec / 1000 ;
    tv.tv_usec = (tmo_millisec % 1000) * 1000;
    setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) ) ;

    int count = recv( socket, buffer, length, flags ) ;
    if( count < 0 (errno == EAGAIN || errno == EWOULDBLOCK) )
    {
        count = 0 ;
    }

    // Restore default blocking
    tv.tv_sec = 0 ;
    tv.tv_usec = 0;
    setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) ) ;

    return count ;
}

【讨论】:

【解决方案3】:
猜你喜欢
  • 1970-01-01
  • 2011-12-22
  • 2015-05-15
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-17
相关资源
最近更新 更多