建立多个连接时如何在C中设置套接字超时?

Posted

技术标签:

【中文标题】建立多个连接时如何在C中设置套接字超时?【英文标题】:How to set socket timeout in C when making multiple connections? 【发布时间】:2011-05-10 01:38:26 【问题描述】:

我正在编写一个简单的程序,它可以与不同的服务器建立多个连接以进行状态检查。所有这些连接都是按需构建的;最多可以同时创建 10 个连接。我不喜欢每个套接字一个线程的想法,所以我将所有这些客户端套接字设为非阻塞,并将它们放入 select() 池中。

效果很好,直到我的客户抱怨目标服务器停止响应时等待时间太长才能得到错误报告。

我检查了论坛中的几个主题。有人建议可以使用 alarm() 信号或在 select() 函数调用中设置超时。但我正在处理多个连接,而不是一个。当一个进程范围的超时信号发生时,我无法区分所有其他连接之间的超时连接。

有没有办法改变系统默认的超时时间?

【问题讨论】:

你的意思是connect()超时时间太长,还是你已经连接了,并且经历了很长一段时间没有什么可读的? @Duck:我的问题是 connect() 超时时间太长。我程序中的每个连接都是暂时的;它应该在执行状态检查握手过程后立即断开连接。在我的情况下,无需单独调整 TCP_KEEP_ALIVE 持续时间。 【参考方案1】:

您可以使用 SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项为任何套接字操作设置超时,如下所示:

    struct timeval timeout;      
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    
    if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
                sizeof timeout) < 0)
        error("setsockopt failed\n");

    if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
                sizeof timeout) < 0)
        error("setsockopt failed\n");
    

编辑:来自setsockoptman page:

SO_SNDTIMEO 是为输出操作设置超时值的选项。它接受一个 struct timeval 参数,其中包含用于限制等待输出操作完成的秒数和微秒数。如果发送操作阻塞了这么长时间,它会返回部分计数,如果没有发送数据,则会返回错误 EWOULDBLOCK。在当前的实现中,每次向协议传递附加数据时都会重新启动此计时器,这意味着该限制适用于输出部分的大小范围从输出的低水位线到高水位线。

SO_RCVTIMEO 是为输入操作设置超时值的选项。它接受一个 struct timeval 参数,其中包含用于限制等待输入操作完成的秒数和微秒数。在当前实现中,每次协议接收到附加数据时都会重新启动该计时器,因此该限制实际上是一个不活动计时器。如果一个接收操作被阻塞了这么长时间而没有接收到额外的数据,它会返回一个短计数或如果没有接收到数据则返回错误 EWOULDBLOCK。 struct timeval 参数必须代表一个正的时间间隔;否则,setsockopt() 返回错误 EDOM。

【讨论】:

你确定这适用于connect()吗?我不相信。 根据pubs.opengroup.org/onlinepubs/009695399/functions/connect.html,“如果无法立即建立连接并且没有为套接字的文件描述符设置 O_NONBLOCK,则 connect() 将阻塞最多未指定的超时间隔,直到连接已建立。”看起来这些超时间隔仅用于读取或写入操作。 1.这不适用于connect() 或一般任何套接字操作。它分别适用于阅读和写作。 2.我看不出这个答案在非阻塞模式下怎么可能是正确的,这就是 OP 所要求的。正确的答案是使用 select() 超时并跟踪哪些套接字在哪些时间发起了连接。 确实适用于connect()。请参阅socket(7): Linux manpage 中的SO_RCVTIMEOSO_SNDTIMEO,其中表示“如果没有传输数据并且已达到超时,则返回-1,并将errno 设置为EAGAINEWOULDBLOCK, 或@ 987654334@ (for connect(2)) 就像套接字被指定为非阻塞一样” 仅在 Linux 4.13 上 SO_SNDTIMEO 设置 connect() 超时,SO_RCVTIMEO 似乎被忽略了。【参考方案2】:

我不确定我是否完全理解这个问题,但猜想它与我的问题有关,我正在使用带有 TCP 套接字通信的 Qt,Windows 和 Linux 都是非阻塞的。..

希望在已连接的客户端失败或完全消失时获得快速通知,而不是等待默认的 900 多秒直到断开信号发出。实现这项工作的技巧是将 SOL_TCP 层的 TCP_USER_TIMEOUT 套接字选项设置为所需的值,以毫秒为单位。

这是一个相当新的选项,请参阅https://www.rfc-editor.org/rfc/rfc5482,但显然它工作正常,在 WinXP、Win7/x64 和 Kubuntu 12.04/x64 上尝试过,我选择的 10 秒结果有点长,但是比我之前尝试过的任何东西都要好;-)

我遇到的唯一问题是找到正确的包含,因为显然这没有添加到标准套接字包含中(还......),所以最后我自己定义它们如下:

#ifdef WIN32
    #include <winsock2.h>
#else
    #include <sys/socket.h>
#endif

#ifndef SOL_TCP
    #define SOL_TCP 6  // socket options TCP level
#endif
#ifndef TCP_USER_TIMEOUT
    #define TCP_USER_TIMEOUT 18  // how long for loss retry before timeout [ms]
#endif

设置此套接字选项仅在客户端已连接时有效,代码行如下所示:

int timeout = 10000;  // user timeout in milliseconds [ms]
setsockopt (fd, SOL_TCP, TCP_USER_TIMEOUT, (char*) &timeout, sizeof (timeout));

并且初始连接的失败被调用connect()时启动的计时器捕获,因为不会有Qt的信号,连接信号不会被引发,因为没有连接,并且也不会发出断开信号,因为还没有连接..

【讨论】:

这个答案在我没有使用 KEEPALIVE 设置解决后帮助了我。谢谢! 谢谢!这帮助我解决了由 TCP 重传计时器引起的 15 分钟断开延迟。【参考方案3】:

你不能实现自己的超时系统吗?

保留超时事件的排序列表,或者更好的是 Heath 建议的优先级堆。在您的 select 或 poll 调用中,使用超时列表顶部的超时值。当该超时到达时,执行附加到该超时的操作。

该操作可能是关闭尚未连接的套接字。

【讨论】:

嗯...这将是一个好主意,但要做到这一点需要一些努力。我希望像 setsockopt() 函数调用可以单独设置连接超时持续时间。顺便说一句,如果我在另一个线程中关闭连接挂起的套接字, select() 会发生什么?它会导致一些线程追逐的情况吗? 这个解决方案是最干净、最强大的解决方案,而且它没有投票?这是我的。 这是我一直这样做的方式,而且效果很好。我怀疑它也比上面提出的其他解决方案更便携。 这是一个很好的方法,+1。我喜欢用优先级堆来做这件事——当我们总是只读取最高优先级的元素时,比保持一个完全排序的列表节省一点 O() 时间。【参考方案4】:

connect 超时必须使用非阻塞套接字来处理(GNU LibC documentation on connect)。您让connect 立即返回,然后使用select 等待连接完成超时。

这里也有解释:Operation now in progress error on connect( function) error。

int wait_on_sock(int sock, long timeout, int r, int w)

    struct timeval tv = 0,0;
    fd_set fdset;
    fd_set *rfds, *wfds;
    int n, so_error;
    unsigned so_len;

    FD_ZERO (&fdset);
    FD_SET  (sock, &fdset);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    TRACES ("wait in progress tv=%ld,%ld ...\n",
            tv.tv_sec, tv.tv_usec);

    if (r) rfds = &fdset; else rfds = NULL;
    if (w) wfds = &fdset; else wfds = NULL;

    TEMP_FAILURE_RETRY (n = select (sock+1, rfds, wfds, NULL, &tv));
    switch (n) 
    case 0:
        ERROR ("wait timed out\n");
        return -errno;
    case -1:
        ERROR_SYS ("error during wait\n");
        return -errno;
    default:
        // select tell us that sock is ready, test it
        so_len = sizeof(so_error);
        so_error = 0;
        getsockopt (sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len);
        if (so_error == 0)
            return 0;
        errno = so_error;
        ERROR_SYS ("wait failed\n");
        return -errno;
    

【讨论】:

很好的答案!我可以知道这里超时的推荐值是多少,即连接超时?

以上是关于建立多个连接时如何在C中设置套接字超时?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 boost asio 中设置阻塞套接字的超时时间?

如何在 Java 1.4 中设置 BufferedReader 和 PrintWriter 的超时时间?

通过 SO_RCVTIMEO 套接字选项在 Ruby 中设置套接字超时

如何在c中设置UDP套接字中的源端口?

在套接字 io 连接中设置用户名

如何配置套接字连接超时