Recv
The recv system call
Background
Learning how to use the sockets API was one of the most rewarding programming experiences I had, but there are a myriad of caveats that can snag up even seasoned developers. Let’s take a look at one of these potential pitfalls and attempt to address it.
The recv system call
For the sake of simplicity, we’ll only consider the case of a non-blocking socket on a single-threaded server.
The recv system call is as follows:
ssize_t recv(int socket, void *buffer, size_t length, int flags);
Moreover, the return values are documented as follows:
These calls return the number of bytes received, or -1 if an error occurred.
For TCP sockets, the return value 0 means the peer has closed its half side of the connection.
What not to do
while ((bytes_read = recv(client_sock, buffer, sizeof(buffer)-1, 0)) > 0) {
// do stuff with buffer
}
Why is this wrong? If we go back to the man page using man 2 recv
, we’ll find this vital snippet:
The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested
The fix
do {
bytes_read = recv(client_sock, &buffer[total_recvd], sizeof(buffer)-total_recvd, 0);
if (bytes_read <= 0) {
break;
} else {
total_recvd += bytes_read;
}
}
while (total_recvd < sizeof(buffer));
There are a few things to note here:
- As long as no error occurred, and the connection wasn’t closed, we keep a running total of the amount of bytes we expected, (i.e.,
total_recvd
). - We want to continue to recv data into the buffer, but we don’t want to overwrite the data we already received, so we advance the
buffer
pointer by the amount of data we’ve already received . - The length supplied to
recv
should be advanced as a function of our buffer size and the amount of data we already received.