C 套接字可以在没有客户端关闭连接的情况下接收 0 个字节吗?

Posted

技术标签:

【中文标题】C 套接字可以在没有客户端关闭连接的情况下接收 0 个字节吗?【英文标题】:Can a C socket recv 0 bytes without the client shutting the connection? 【发布时间】: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 的答案是正确的。 【参考方案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 ;

【讨论】:

请注意,***.com/questions/2876024/… 至少描述了两种替代方法【参考方案3】:

我相信正确答案是“是”,但前提是您提供零长度。否则,您可以从recv() 获得零的唯一方法是对等方已正常断开连接。由于网络状况,您当然不会得到它。

支持来自 Posix 的引用:

    The recv() function is equivalent to recvfrom() with null pointer address and address_len arguments, and to read() if the socket argument refers to a socket and the flags argument is 0。 Before any action described below is taken, and if nbyte is zero, the read() function may detect and return errors as described below. In the absence of errors, or if error detection is not performed, the read() function shall return zero and have no other results.

【讨论】:

以上是关于C 套接字可以在没有客户端关闭连接的情况下接收 0 个字节吗?的主要内容,如果未能解决你的问题,请参考以下文章

socket编程server端程序接收client连接后accept套接字值为0正常吗?

如何在不关闭服务器套接字的情况下在客户端获得确认?

socket编程server端程序接收client连接后accept套接字值为0正常吗?

Android - 客户端关闭连接发送图像,然后接收回字符串

C服务器套接字关闭但客户端套接字仍然可以发送另外两个包

Java 套接字服务器只显示连接关闭后收到的消息