在學習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));