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

时间:2016-06-24 20:32:12

标签: c sockets server recv

我在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字节,从而导致我的代码过早地退出循环?手册页含糊不清。

2 个答案:

答案 0 :(得分:7)

TL; DR:POSIX说&#34; no&#34;。

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

  

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

     

[...]

     

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

因此,POSIX正好允许三种选择:

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

  • 没有收到来自对等方的消息,我们相信没有任何消息即将发布,因为对等方已经(在recv()返回时)执行了有序的连接关闭。 recv()返回0.

  • &#34;否则&#34;发生了别的事。 recv()返回-1。

在任何情况下,如果没有可用数据并且套接字上没有可用的错误条件,recv()将阻塞,除非套接字处于非阻塞模式。在非阻塞模式下,如果在调用recv()时既没有数据也没有错误条件,那么它将落入&#34;否则&#34;情况下。

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

答案 1 :(得分:1)

如果使用setsockopt()设置超时并且超时到期且未收到任何数据,则recv()将返回-1,不会缓冲任何数据,并且将设置errnoEAGAINEWOULDBLOCK(这些可能具有相同的值)。套接字将继续打开。

例如:

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 ;
}