c-SO_ SNDTIMEO和SO_ RCVTIMEO的意外行为



我正试图在Linux上使用setsockoptSO_SNDTIMEOSO_RCVTIMEO来设置阻塞TCP套接字的超时。但由于某种原因,我在等待recv呼叫时被锁了。

考虑一个最小的例子。为了可读性,我把它缩短了一点,完整的代码在这个要点中可用。我创建了一个服务器套接字,并使用setsockopt设置超时。接下来,服务器与客户端交换消息。一段时间后,客户端中断数据交换并关闭套接字。但服务器仍在等待阻止recv。但是,我希望它在超时到期时中止recv

客户端和服务器使用的网络功能:

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

服务器:

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

客户:

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);

可能出了什么问题?

是否可以在不使用selectpoll和信号的情况下,以这种方式实现recv的超时?

谢谢!

您正在循环读取:

while ((remain_data > 0) &&
((recv_nb = recv(sock, data + offset, remain_data, 0)) >= 0)) {
remain_data -= recv_nb;
offset += recv_nb;
}

而来自man readread返回:

If no messages are available to be received and the peer has
performed an orderly shutdown, recv() shall return 0.

所以你只是在无休止地循环。

您应该在程序中正确处理errno。当SNDTIMEO内容超时时,您得到:the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) ...

当信号中断时,仍然可以返回EAGAIN,因此仅使用通常的select()poll()3秒可能会更简单。如果没有,请自己测量时间,并将超时设置为您想要的最大超时,然后测量经过了多少时间。沿线:

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

使用strace和调试器等工具来调试程序。

最新更新