I’m trying to set the timeout for the blocking TCP socket on Linux using setsockopt
with SO_SNDTIMEO
and SO_RCVTIMEO
. But for some reason I get a lock while waiting on recv
call.
Consider the minimal example. I’ve shortened it a bit for readability, the full code is available in this gist. I create a server socket and set the timeouts using setsockopt
. Next, the server exchanges messages with the client. After a while, the client interrupts data exchange and closes the socket. But the server still waiting in blocking recv
. However, I expect it to abort recv
when the timeout expires.
Network functions used by the client and server:
int set_timeout(int fd, int sec) { struct timeval timeout; timeout.tv_sec = sec; timeout.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) { return -1; } if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) { return -1; } return 0; } int blocking_recv(int sock, char *data, size_t size) { size_t remain_data = size; ssize_t recv_nb; int offset = 0; while ((remain_data > 0) && ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) { remain_data -= recv_nb; offset += recv_nb; } if (recv_nb == -1 || remain_data > 0) { return -1; } return 0; } int blocking_send(int sock, char *buf, size_t size) { size_t total = 0; int remain = size; int n; while (total < size) { n = send(sock, buf + total, remain, 0); if (n == -1) { break; } total += n; remain -= n; } return (total == size) ? 0 : -1; }
Server:
while (1) { struct sockaddr_in caddr; size_t len = sizeof(caddr); if ((client_fd = accept(server_fd, (struct sockaddr *)&caddr, (socklen_t *)&len)) < 0) { continue; } // Set 3 seconds timeout using SO_{SND,RCV}TIMEO if (set_timeout(client_fd, 3) != 0) { continue; } // Send and receive data in the loop. We assume that the client is stuck, // we'll break the connection using a timeout. while (1) { char in_data[data_size]; char *out_data = "test"; if (blocking_recv(client_fd, in_data, data_size) != 0) break; if (blocking_send(client_fd, out_data, data_size) != 0) break; } }
Client:
int attempts_left = 5; while (--attempts_left > 0) { char in_data[6]; char *out_data = "test"; if (blocking_send(fd, out_data, 6) != 0) break; if (blocking_recv(fd, in_data, 6) != 0) break; sleep(1); } close(fd);
What could be wrong?
Is it possible to implement the timeout for recv
in this way, without using select
, poll
and signals?
Thanks!
Advertisement
Answer
You are looping your reading:
while ((remain_data > 0) && ((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) { remain_data -= recv_nb; offset += recv_nb; }
yet read
from man read
returns:
If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0.
So you are just looping endlessly.
You should correctly handle errno
in your program. when SNDTIMEO
stuff timeouts, you get: the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) ...
.
Still EAGAIN
can be returned when signal interrupts, so just the usual select()
or poll()
for 3 seconds would be probably simpler. If not, measure time yourself anyway and set the timeout to the max timeout you want and measure how much time has passed. Along:
timeout = now + 3 seconds. // check timeout yourself while (timeout_not_expired(&timeout)) { // set timeout for the __next__ recv operation set_recv_timeout(timeout_to_expire(&timeout)); ret = recv(); if (ret == -1 && errno == EAGAIN) { continue; } if (ret == -1) { /* handle errror */ } if (ret == 0) { /* closed */ } /* actually received stuff */ }
Use tools like strace
and a debugger to debug your programs.