在客户端不关闭连接的情况下,C 套接字可以接收 0 字节吗?
Can a C socket recv 0 bytes without the client shutting the connection?
我用 C 实现了一个 Web 服务器。它在连接的阻塞套接字上调用 recv()
以接收传入的 HTTP 请求。 Linux 手册页对阻塞套接字上的 recv()
进行了以下说明:
If no messages are available at the socket, the receive calls wait for a message to arrive...The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested.
...
These calls return the number of bytes received, or -1 if an error occurred...The return value will be 0 when the peer has performed an orderly shutdown.
因此,为了接收完整的请求,我的服务器代码包含以下形式的循环:
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()
到 return 0 字节并且客户端没有执行有序关闭,从而导致我的代码过早退出循环?手册页含糊不清。
TL;DR:POSIX 说 "no"。
我不认为手册页如此不清楚,但 POSIX's description 可能更清楚一点:
The recv()
function shall receive a message from a connection-mode or connectionless-mode socket.
[...]
Upon successful completion, recv()
shall return the length of the message in bytes. If no messages are available to be received and the peer has performed an orderly shutdown, recv()
shall return 0. Otherwise, -1 shall be returned and errno
set to indicate the error.
因此,POSIX正好允许三种选择:
recv()
成功收到一条消息,returns 消息长度。根据定义,长度是非零的,因为如果没有接收到字节,那么就没有接收到消息。 recv()
因此 returns 一个大于 0 的值。
没有收到来自对等方的消息,我们有信心 none 即将到来,因为对等方已经(到 recv()
returns 时)执行了有序的连接关闭。 recv()
returns 0.
"otherwise" 发生了其他事情。 recv()
returns -1.
在任何情况下,recv()
如果套接字上没有可用数据且没有错误条件可用,则将阻塞,除非套接字处于非阻塞模式。在非阻塞模式下,如果调用recv()
时既没有数据也没有错误条件可用,则属于"otherwise"情况。
不能完全排除给定系统不符合 POSIX 的可能性,但您必须以某种方式决定如何解释函数结果。如果您在声称符合 POSIX 的系统上调用 POSIX 定义的函数(至少就该函数而言),那么依赖 POSIX 语义就很难争论.
如果使用setsockopt()
设置了超时,并且超时到期并且没有接收到数据,recv()
将return-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 ;
}
我认为正确答案是 'yes',但前提是您提供的长度为零。否则,您可以从 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 实现了一个 Web 服务器。它在连接的阻塞套接字上调用 recv()
以接收传入的 HTTP 请求。 Linux 手册页对阻塞套接字上的 recv()
进行了以下说明:
If no messages are available at the socket, the receive calls wait for a message to arrive...The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested.
...
These calls return the number of bytes received, or -1 if an error occurred...The return value will be 0 when the peer has performed an orderly shutdown.
因此,为了接收完整的请求,我的服务器代码包含以下形式的循环:
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()
到 return 0 字节并且客户端没有执行有序关闭,从而导致我的代码过早退出循环?手册页含糊不清。
TL;DR:POSIX 说 "no"。
我不认为手册页如此不清楚,但 POSIX's description 可能更清楚一点:
The
recv()
function shall receive a message from a connection-mode or connectionless-mode socket.[...]
Upon successful completion,
recv()
shall return the length of the message in bytes. If no messages are available to be received and the peer has performed an orderly shutdown,recv()
shall return 0. Otherwise, -1 shall be returned anderrno
set to indicate the error.
因此,POSIX正好允许三种选择:
recv()
成功收到一条消息,returns 消息长度。根据定义,长度是非零的,因为如果没有接收到字节,那么就没有接收到消息。recv()
因此 returns 一个大于 0 的值。没有收到来自对等方的消息,我们有信心 none 即将到来,因为对等方已经(到
recv()
returns 时)执行了有序的连接关闭。recv()
returns 0."otherwise" 发生了其他事情。
recv()
returns -1.
在任何情况下,recv()
如果套接字上没有可用数据且没有错误条件可用,则将阻塞,除非套接字处于非阻塞模式。在非阻塞模式下,如果调用recv()
时既没有数据也没有错误条件可用,则属于"otherwise"情况。
不能完全排除给定系统不符合 POSIX 的可能性,但您必须以某种方式决定如何解释函数结果。如果您在声称符合 POSIX 的系统上调用 POSIX 定义的函数(至少就该函数而言),那么依赖 POSIX 语义就很难争论.
如果使用setsockopt()
设置了超时,并且超时到期并且没有接收到数据,recv()
将return-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 ;
}
我认为正确答案是 'yes',但前提是您提供的长度为零。否则,您可以从 recv()
获得零的唯一方法是对等方正常断开连接。网络问题你肯定收不到。
来自 Posix 的支持引用:
- The
recv()
function is equivalent torecvfrom()
with null pointer address and address_len arguments, and toread()
if thesocket
argument refers to a socket and the flags argument is 0. - Before any action described below is taken, and if
nbyte
is zero, theread()
function may detect and return errors as described below. In the absence of errors, or if error detection is not performed, theread()
function shall return zero and have no other results.