在学习TCP超时设置的时候,发现网上没有完整的超时介绍,遂总结一下。TCP超时总共分为3类:connectTimeout, writeTimeout, readTimeout(连接超时,读超时,写超时)。下面分别介绍如何设置这三种超时。
1. 连接超时
在TCP调用connect函数时,TCP的建立需要3次握手,从客户端发出SYS信号之后开始等待,超过超时时间即连接失败,connect函数不再等待,直接返回。这个时间称为超时时间。超时时间系统是有最大限制的,以Linux系统为例,调用命令:sysctl net.ipv4.tcp_syn_retries可以查看系统设置的connectTimeout最大值。返回值:
4:timeout是31s
5: timeout是75s
6: timeout是127s
但是有时我们希望设置自己的connectTimeout时间(注:此时间必须比系统timeout时间短,否则系统会截成系统timeout时间)。
1.1 使用alarm函数
在设置超时时间时,可以采用alarm函数,具体如下:
//超时处理函数 void alarm_handler(int sig) { printf("connect timeout"); return; } int main() { ... signal(SIGALRM, alarm_handler) alarm(5); //设置超时时间5s int rc=connect(...); //调用connect函数 alarm(0); if(rc<0){ if(errno==EINTR){ //connect超时 } } }
这种方法可能有以下缺陷:
(1) 有些UNIX操作系统在信号处理程序返回之后可能重启connect调用;
(2) 假如connect成功,但是此时alarm定时到了,此时程序仍然会终止。
1.2 使用select函数
使用select函数,监听套接字是否有读或写性质的变化(实际上监视写性质的变化就行了,因为一旦连接成功,套接字一定是可写的)。下面看伪代码:
1 int main() 2 { 3 int sock; 4 5 sock=socket(AF_INET,SOCK_STREAM,0); //1.调用socket函数 6 7 //2. 将sock设置成非阻塞 8 9 //3.调用connect函数 10 int rc=connect(sock,...); 11 /***************** 12 调用connect函数后,因为设置成非阻塞,会有三种典型情况: 13 1. rc=0,连接成功 14 2. rc!=0 && errno=EINPROCRESS,说明还未连接成功 15 3. rc!0 && errno=!EINPROCRESS,连接失败 16 ******************/ 17 if(rc==0){ 18 连接成功,将sock设置成阻塞; 19 执行后续客户端程序; 20 } 21 else if(rc!=0 && error!=EINPROCRESS) 22 连接失败,直接返回; 23 else{ 24 fd_set rdevent,wrevent,exevent; //这里可以只检测写事件,即wrevent 25 FD_ZERO(&rdevent); 26 FD_SET(sock,&rdevent); 27 wr=rdevent; 28 exevent=rdevent; //设置读监视,写监视以及异常监视 29 tv.tv_sec=5; 30 tv.tv_usecc=0; //设置超时时间5s,此部分相关操作均可在select函数使用方法中查询 31 rc=select(sock+1,&rdevent,&wrevent,&exevent,&tv); 32 if(rc<0) 33 select函数错误; 34 else if(rc==0) 35 select函数超时,即连接超时connectTimeout 36 else{ //有监测信号返回 37 //此时检测是否是连接成功 38 if(!FD_ISSET(sock,&rdevent) && !FD_ISSET(sock,&wrevent)) //既不可读,也不可写,一定是连接错误 39 int err; 40 int len=sizeof(err); 41 if(getsockopt(sock,SOL_SOCKET,SO_ERROR,&err,&len)<0) 42 调用getsockopt()函数本身的错误; 43 if(err!=0) 44 说明连接错误,退出; 45 else 46 连接成功,设置sock为阻塞,开始客户端处理函数; 47 } 48 } 49 }
最关键的判断有两处,一是调用connect()函数后有三种可能情况,见上面12-15行;二是slelect检测到了性质变化,调用getsockopt()函数,如果返回的错误err=0,说明没错,连接建立,否则连接没建立,见上面41-46行。
注:若conenct函数调用失败之后,不能马上再次调用connect函数,必须先关闭套接字。
2. writeTimeout和readTimeout超时
读写超时设置要用到函数setsockopt(),这个比较简单,直接在客户端设置一下就可以了,如下:
tv.tv_sec=3; tv.tv_usec=0; setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)); setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));