如何在C中设置套接字超时进行多个连接?
我正在写一个简单的程序,使多个连接到不同的服务器进行状态检查。 所有这些连接都是按需build立的。 最多可同时创build10个连接。 我不喜欢每个套接字一个线程的想法,所以我将所有这些客户端套接字都设置为Non-Blocking,然后将它们放入一个select()池中。
它工作得很好,直到我的客户抱怨说,等待时间太长,才能得到目标服务器停止响应的错误报告。
我在论坛上查了几个主题。 有人build议可以使用alarm()信号或在select()函数调用中设置超时。 但我正在处理多个连接,而不是一个。 当发生进程宽度超时信号时,我无法区分所有其他连接之间的超时连接。
有无论如何改变系统默认的超时时间?
您可以使用SO_RCVTIMEO和SO_SNDTIMEO套接字选项为任何套接字操作设置超时,如下所示:
struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) error("setsockopt failed\n"); if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) error("setsockopt failed\n");
编辑:从setsockopt
手册页 :
SO_SNDTIMEO
是为输出操作设置超时值的选项。 它接受一个struct timeval参数,其中用于限制等待输出操作完成的秒数和微秒数。 如果一个发送操作被阻塞了这么多时间,它将返回一个部分计数或者在没有数据发送的情况下返回错误EWOULDBLOCK。 在当前的实施中,每当附加数据被传送到协议时,该计时器重启,意味着该限制适用于从低水位标记到高水位标记的输出部分。
SO_RCVTIMEO
是为input操作设置超时值的选项。 它接受一个struct timeval参数,其中用于限制等待input操作完成的秒数和微秒数。 在当前的实现中,每当协议接收到附加数据时重新启动该定时器,因此该限制实际上是不活动定时器。 如果接收操作在没有接收到附加数据的情况下被阻塞了很长时间,则返回一个短计数或者在没有收到数据的情况下返回错误EWOULDBLOCK。 struct timeval参数必须表示正的时间间隔; 否则,setsockopt()返回错误EDOM。
我不知道我是否完全理解这个问题,但是猜想它与我所使用的相关,我正在使用Qt与TCP套接字通信,所有的非阻塞,Windows和Linux。
当一个已经连接的客户端发生故障或者完全消失时,想要得到一个快速的通知,而不是等待默认的900+秒,直到断开信号提高。 实现这个工作的技巧是将SOL_TCP层的TCP_USER_TIMEOUT套接字选项设置为所需的值(以毫秒为单位)。
这是一个相当新的select,请参阅http://tools.ietf.org/html/rfc5482 ,但显然它工作正常,尝试与WinXP,Win7 / x64和Kubuntu 12.04 / x64,我select了10秒要稍微长一些,但是比我之前尝试过的其他东西好多了;-)
我碰到的唯一的问题是find正确的包括,显然这不是添加到标准套接字包括(但..),所以最后我定义他们自己如下:
#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的信号,连接信号将不会被提升,因为没有连接,断开信号将会也没有提出,因为还没有连接..
你不能实现你自己的超时系统吗?
保留一个sorting的列表,或者更好的是希思提出的一个优先级堆栈超时事件。 在您的select或轮询呼叫中,使用超时列表顶部的超时值。 当超时到达时,执行该操作附加到超时。
该操作可能会closures尚未连接的套接字。
connect
超时必须用一个非阻塞的套接字来处理(在connect
上的GNU LibC 文档 )。 您将connect
立即返回,然后使用select
等待连接完成超时。
这也在这里解释: 正在进行的操作错误连接(function)错误 。
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; } }
当然,第一个答案是最好的答案。 我能添加一些东西吗?
…
2 setsockopt之后你可以控制客户端是否通过了超时testing,或者失败了:
之后
n = readline(sockd, recvline, MAXLINE);
你必须插入
if (n <= 0){ if(write(sockd,"ERROR. Timeout di 5sec scaduto, sii piu' veloce\n",MAXLINE)<0) err_sys("errore nella write"); close(sockd); sockd = 0; break; }