8

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.

Community
  • 1
  • 1
dmoon1221
  • 287
  • 1
  • 3
  • 9
  • Is this a "can it" or "how do I" question? That is do you want to be able to receive zero bytes or just want to know whether it can happen? – Clifford Jun 24 '16 at 22:27
  • 1
    @Clifford Just a "can it" question – dmoon1221 Jun 24 '16 at 23:06
  • Ok. Despite the accepted and up-voted answer saying emphatically "no", the answer is clearly "yes", although between the title and the body text, your question does appear to conflate *receiving* zero bytes with *returning a value* of zero; they are not precisely the same thing. That is the return value only represents the number of bytes received when it is > 0. – Clifford Jun 25 '16 at 07:43
  • @Clifford You're right, the phrasing of my question was ambiguous. My apologies. More precisely, my question was whether it would be possible for recv() to return the value 0 to indicate that it had received 0 bytes from the client but without the client performing a shutdown. The reason for this confusion, as I explain in a comment to John's answer, was a misunderstanding of the concept of a "message". – dmoon1221 Jun 25 '16 at 16:36
  • To my more precisely phrased question, where the observed behavior is simply a return _value_ of 0, I believe John's answer is the correct one. – dmoon1221 Jun 25 '16 at 16:39
  • I shall let my answer stand - it may be a useful answer to a different question than you intended, and unless or until you edit it it is still a valid interpretation of the question - i.e. ("*...revc 0 bytes...*" vs "*...revc return 0...*", and "*...`recv()`to return 0 bytes...*" vs "*...`recv()` to return 0 ...*". If you do edit it I shall probably delete my answer as off-topic. – Clifford Jun 26 '16 at 11:34

3 Answers3

11

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    The question is perhaps whether zero bytes can be received rather then whether `recv()` can return 0 as a return value. – Clifford Jun 24 '16 at 22:13
  • @Clifford, perhaps so, and if indeed that is the case then this answer addresses it where it says "if no bytes were received then no message was received". – John Bollinger Jun 24 '16 at 22:36
  • Yes, but the lack of clarity in the documentation if there is any is that `recv()` makes no reference to the possibility of a timeout. The first line says "*POSIX says "no"*" which is an emphatic answer, but not correct in the strictest interpretation of the question. If it were not for this first line, I'd have no comment. The answer if in fact in the "*something else happened*" which does little to clarify. – Clifford Jun 24 '16 at 22:49
  • 3
    The first point is incorrect in the case of a `SOCK_DGRAM` or `SOCK_SEQPACKET` socket, which CAN have 0-length messages. However, it would seem the OP is implicitly asking about `SOCK_STREAM` as that is what is normally used to transport HTTP – Chris Dodd Jun 24 '16 at 22:55
  • 1
    Ah, I see my confusion now. I misunderstood the concept of a "message", thinking the man pages were referring to the entire HTTP request. I thought `recv()` would only block until it began receiving the very start of the HTTP request, but could return immediately (possibly on 0 bytes of received data) on any subsequent `recv()` calls. But this doesn't make sense, since the received data is just a stream of uninterpreted bytes at this point. Thanks, all. – dmoon1221 Jun 24 '16 at 23:02
  • What happens if there is a socket buffer overflow and the read buffer at receiver/subscriber end and write buffer at publisher end gets field. In such cases will the receive bytes be 0 or should be negative value ? – Invictus May 15 '19 at 07:22
  • @Invictus, what about those particular circumstances makes you think that they are not already addressed by this answer? But do note that for a stream-oriented socket such as is the subject here, the concept of "message" is very weak. This is one of the things that seems to confuse people. Any positive number of bytes that `recv()` can transfer is a message for the purposes of a stream socket. – John Bollinger May 15 '19 at 12:00
1

If a timeout is set using setsockopt() and the timeout expires and no data has been received, recv() will return -1, no data will be buffered, and errno will be set to EAGAIN or EWOULDBLOCK (these may have the same value). Critically the socket will remain open.

For example:

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.

It might be useful to generalise this functionality for reuse:

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 ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Note that at least two alternative methods are described at http://stackoverflow.com/questions/2876024/linux-is-there-a-read-or-recv-from-socket-with-timeout – Clifford Jun 25 '16 at 07:46
0

I believe the correct answer is 'yes', but only if you supply a zero length. Otherwise the only way you can get zero from recv() is if the peer has disconnected gracefully. You certainly don't get it due to network conditions.

Supporting citations from Posix:

  1. 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.
  2. 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.
user207421
  • 305,947
  • 44
  • 307
  • 483