我们都知道connect函数执行TCP协议中的三次握手过程,在默认情况下,connect是阻塞的。
当通过tcp客户端向未知ip发起connect请求时,tcpdump抓包情况:
21:13:15.545917 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553264049 ecr 0,nop,wscale 7], length 0
21:13:16.550200 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553265054 ecr 0,nop,wscale 7], length 0
21:13:18.566704 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553267070 ecr 0,nop,wscale 7], length 0
21:13:22.599110 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553271103 ecr 0,nop,wscale 7], length 0
21:13:30.790961 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553279294 ecr 0,nop,wscale 7], length 0
21:13:36.548172 IP 144.34.160.165.16clouds.com.12345 > 192.168.237.128.57368: Flags [R.], seq 414153142, ack 287971295, win 64240, length 0
可见,在整个connect阻塞过程中,客户端会不断重启SYN报文的发送,发送间隔逐渐增大,大约在20s后connect才超时退出。
当网络连接数量巨大是,阻塞的connect会导致效率非常低下。我们可以采用以下两种方式来设置其超时时间。
信号中断方式
在调用connect()前,调用alarm()设置一个超时值,在指定时间后产生SIGALARM信号,中断可能阻塞的connect。
我们采用sigaction()来实现信号处理函数signal(),设置SIGALARM信号中断的系统调用为不自动重启,这样才能达到超时退出的目的。
typedef void Sigfunc (int);
static void connect_sigfun(int signo)
{
return;
}
Sigfunc* signal(int signo, Sigfunc * func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(signo == SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; //设置SIGALARM信号中断的调用不自动重启
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
connect函数实现:
int connect_timeo_signal(int sockfd, const sockaddr * sockaddr, socklen_t len, int nsec)
{
Sigfunc * sigfunc;
int n;
sigfunc = signal(SIGALRM, connect_sigfun);
alarm(nsec);
if((n = connect(sockfd, sockaddr, len)) < 0)
{
close(sockfd);
if(errno == EINTR)
{
errno = ETIMEDOUT;
cout<<"connect timeout!"<<endl;
}
}
alarm(0);
signal(SIGALRM, sigfunc);
return n;
}
非阻塞方式
在非阻塞套接字上调用connect,会立即返回一个EINPROGRESS错误,但三次握手的操作可以继续执行,可以接着采用带超时的select检测连接的建立情况。
connect函数实现:
int connect_timeo_nonb(int sockfd, const sockaddr * sockaddr, socklen_t len, int nsec)
{
int op, n, err;
socklen_t elen;
fd_set rset, wset;
struct timeval tval;
if( (op = fcntl(sockfd, F_GETFL)) < 0)
{
perror("fcntl error");
return -1;
}
if(fcntl(sockfd, F_SETFL, op | O_NONBLOCK) < 0)
{
perror("fcntl error");
return -1;
}
if((n = connect(sockfd, sockaddr, len)) < 0)
if(errno != EINPROGRESS)
return -1;
if(n == 0)
goto end;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(sockfd, &rset);
FD_SET(sockfd, &wset);
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0)
{
close(sockfd);
errno = ETIMEDOUT;
return -1;
}
if (n < 0)
{
perror("select error");
close(sockfd);
return -1;
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
{
elen = sizeof(errno);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &errno, &elen) < 0)
return -1;
}
end:
if(fcntl(sockfd, F_SETFL, op) < 0)
perror("fcntl error");
if(errno)
{
close(sockfd);
errno = err;
return -1;
}
return 0;
}
其中,首先设置套接字描述符为非阻塞;然后connect,并select监听套接字描述符的读写状态,若返回0表示超时;当可写或可读后,调用getsockopt()获取套接字的待处理错误,如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,这个值是错误所对应的errno值。
验证:
fuzhuo@fuzhuo-virtual-machine:~/FZ/code/test/unp$ ./client 144.34.160.165
connect failed: Connection timed out
在默认情况下,Linux中的connect函数是阻塞的,尤其在网络连接数量大时效率低下。本文介绍了两种设置connect超时的方法:1) 通过信号中断方式,使用alarm()设置超时并用sigaction()处理SIGALARM信号;2) 利用非阻塞方式,将套接字设为非阻塞,结合select()进行超时判断和连接状态检查。

425

被折叠的 条评论
为什么被折叠?



