Linux:是否有带超时的套接字读取或接收函数?

133

我该如何尝试使用超时从套接字中读取数据? 我知道,select、pselect和poll都有一个超时字段,但是使用它们会禁用tcp reno堆栈中的“tcp快速路径”。

我唯一想到的办法是在循环中使用recv(fd, ..., MSG_DONTWAIT)。


还有一种使用线程的选项 :) 但仍需要线程信号。 - osgx
5个回答

241

您可以使用setsockopt函数在接收操作上设置超时时间:

SO_RCVTIMEO

设置超时值,指定输入函数等待完成的最长时间。它接受一个timeval结构体,其中秒和微秒数指定等待输入操作完成的限制时间。如果接收操作已经阻塞了这么长时间而没有接收到其他数据,则它将返回带有部分计数或errno设置为[EAGAIN]或[EWOULDBLOCK](如果没有接收到数据)。此选项的默认值为零,表示接收操作不会超时。这个选项需要一个timeval结构体。请注意,不是所有的实现都允许设置这个选项。

// LINUX
struct timeval tv;
tv.tv_sec = timeout_in_seconds;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

// WINDOWS
DWORD timeout = timeout_in_seconds * 1000;
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout);

// MAC OS X (identical to Linux)
struct timeval tv;
tv.tv_sec = timeout_in_seconds;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

据报道,在 Windows 上应该在调用 bind 之前执行此操作。我通过实验验证,在 Linux 和 OS X 上无论是在 bind 之前还是之后执行都可以。


8
这个回答真是帮了我大忙。我被那个复杂的“选择”程序卡住了,毫无进展。但这个方法立即生效了,简单多了。 - MiloDC
现在明白为什么 Windows 上的超时不起作用了,因为我正在使用 Linux 的代码。谢谢。如果 Windows 没有使用 struct timeval tv;,那么这是否意味着 select() 也无法工作?我尝试将我的 select() 代码移植到 Windows 上,但它立即超时,看起来它忽略了我设置的 timeval 值。 - kuchi
1
我将超时值设置为5秒。为什么每个读取周期始终需要5秒,无论是否有传入数据? - Han
2
即使进行了绑定操作,此方法在Windows上仍然有效。已在Windows 10上尝试过。 - cahit beyaz
这会导致丢失UDP包吗?例如,当接收到新消息时超时刚好触发? - 463035818_is_not_a_number
2
@user463035818 这个答案声称它不应该。 - Tomeamis

28

这里是一些简单的代码,可以使用C中的poll函数为您的recv函数添加超时:

struct pollfd fd;
int ret;

fd.fd = mySocket; // your socket handler 
fd.events = POLLIN;
ret = poll(&fd, 1, 1000); // 1 second for timeout
switch (ret) {
    case -1:
        // Error
        break;
    case 0:
        // Timeout 
        break;
    default:
        recv(mySocket,buf,sizeof(buf), 0); // get your data
        break;
}

这不会按预期工作。poll 函数将等待接收至少一个字节或超时,而调用 recv 函数时,它将等待 sizeof(buf) 字节,如果此计数尚未到达,则会再次阻塞,但这次没有超时。 - LoPiTaL
3
根据 https://man7.org/linux/man-pages/man2/recv.2.html 和 https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv,recv 函数应该返回最多 sizeof(buf) 字节的任意可用数据。因此,这个解决方案应该是可行的。 - GILGAMESH

2

// 在 Windows 上进行绑定操作后也可以使用

DWORD timeout = timeout_in_seconds * 1000;
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout);

1
安装一个 SIGALRM 的处理程序,然后在常规阻塞的 recv() 之前使用 alarm()ualarm()。如果闹钟响了,recv() 将返回一个带有 errno 设置为 EINTR 的错误。

12
警报(和信号)不是完成此任务的正确方式。如果我想使用TCP快速路径,那么我需要最小化延迟。信号很慢。 - osgx
3
仅当超时发生时,信号才会出现。 - David Schwartz

-3

LINUX

struct timeval tv;
tv.tv_sec = 30;        // 30 Secs Timeout
tv.tv_usec = 0;        // Not init'ing this can cause strange errors
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv,sizeof(struct timeval));

WINDOWS

DWORD timeout = SOCKET_READ_TIMEOUT_SEC * 1000;
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));

注意:您需要在bind()函数调用之前设置此选项以确保正确运行


5
这个问题已经在几年前得到了回答。你的解决方案带来了什么新价值? - Maciej Jureczko
1
你在bind()函数调用之前设置了这个选项,以便正确运行此部分,但这一点没有在答案中提到。 - vivek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接