我在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字节,从而导致我的代码过早地退出循环?手册页含糊不清。
答案 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,不会缓冲任何数据,并且将设置errno
到EAGAIN
或EWOULDBLOCK
(这些可能具有相同的值)。套接字将继续打开。
例如:
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 ;
}