在学习 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 函数, 监听套接字是否有读或写性质的变化 (实际上监视写性质的变化就行了, 因为一旦连接成功, 套接字一定是可写的). 下面看伪代码:
- int main()
- {
- int sock;
- sock=socket(AF_INET,SOCK_STREAM,0); //1. 调用 socket 函数
- //2. 将 sock 设置成非阻塞
- //3. 调用 connect 函数
- int rc=connect(sock,...);
- /*****************
- 调用 connect 函数后, 因为设置成非阻塞, 会有三种典型情况:
- 1. rc=0, 连接成功
- 2. rc!=0 && errno=EINPROCRESS, 说明还未连接成功
- 3. rc!0 && errno=!EINPROCRESS, 连接失败
- ******************/
- if(rc==0){
连接成功, 将 sock 设置成阻塞;
执行后续客户端程序;
- }
- else if(rc!=0 && error!=EINPROCRESS)
连接失败, 直接返回;
- else{
- fd_set rdevent,wrevent,exevent; // 这里可以只检测写事件, 即 wrevent
- FD_ZERO(&rdevent);
- FD_SET(sock,&rdevent);
- wr=rdevent;
- exevent=rdevent; // 设置读监视, 写监视以及异常监视
- tv.tv_sec=5;
- tv.tv_usecc=0; // 设置超时时间 5s, 此部分相关操作均可在 select 函数使用方法中查询
- rc=select(sock+1,&rdevent,&wrevent,&exevent,&tv);
- if(rc<0)
select 函数错误;
else if(rc==0)
select 函数超时, 即连接超时 connectTimeout
- else{ // 有监测信号返回
- // 此时检测是否是连接成功
- if(!FD_ISSET(sock,&rdevent) && !FD_ISSET(sock,&wrevent)) // 既不可读, 也不可写, 一定是连接错误
- int err;
- int len=sizeof(err);
- if(getsockopt(sock,SOL_SOCKET,SO_ERROR,&err,&len)<0)
调用 getsockopt() 函数本身的错误;
if(err!=0)
说明连接错误, 退出;
else
连接成功, 设置 sock 为阻塞, 开始客户端处理函数;
- }
- }
- }
最关键的判断有两处, 一是调用 connect() 函数后有三种可能情况, 见上面 12-15 行; 二是 slelect 检测到了性质变化, 调用 getsockopt() 函数, 如果返回的错误 err=0, 说明没错, 连接建立, 否则连接没建立, 见上面 41-46 行.
注: 若 conenct 函数调用失败之后, 不能马上再次调用 connect 函数, 必须先关闭套接字.
2. writeTimeout 和 readTimeout 超时
来源: http://www.bubuko.com/infodetail-3452689.html