Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a C socket recv 0 bytes without the client shutting the connection?

I've implemented a web server in C. It calls recv() on a connected, blocking socket to receive an incoming HTTP request. The Linux man pages state the following about recv() on a blocking socket:

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.

Hence, to receive the full request, my server code contains a loop of the following form:

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

My question: is it possible for recv() to return 0 bytes due to network issues and without the client performing an orderly shutdown, thereby causing my code to exit the loop prematurely? The man pages are ambiguous.

like image 795
dmoon1221 Avatar asked Jun 24 '16 20:06

dmoon1221


1 Answers

TL;DR: POSIX says "no".

I don't think the man page is so unclear, but POSIX's description is perhaps a bit more clear:

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.

Thus, there are exactly three alternatives allowed by POSIX:

  • recv() successfully receives a message and returns its length. The length is nonzero by definition, for if no bytes were received then no message was received. recv() therefore returns a value greater than 0.

  • no message was received from the peer, and we are confident that none will be forthcoming because the peer has (by the time recv() returns) performed an orderly shutdown of the connection. recv() returns 0.

  • "otherwise" something else happened. recv() returns -1.

In any event, recv() will block if no data are available and no error condition is available at the socket, unless the socket is in non-blocking mode. In non-blocking mode, if there is neither data nor error condition available when recv() is called, that falls into the "otherwise" case.

One cannot altogether rule out that a given system will fail to comply with POSIX, but you have to decide somehow how you're going to interpret function results. If you're calling a POSIX-defined function on a system that claims to conform to POSIX (at least with respect to that function) then it's hard to argue with relying on POSIX semantics.

like image 76
John Bollinger Avatar answered Sep 22 '22 05:09

John Bollinger