[轉]socket超時設置詳解(connect/read/write)


https://blog.csdn.net/gettogetto/article/details/77105005

一.基本概念(摘自:《unix網絡編程》卷1 14.2 套接字超時)
在涉及套接字的I/O操作上設置超時的方法有以下三種
(1)調用alarm,它在指定超時期滿時產生SIGALARM。這個方法涉及信號處理,而信號處理在不同的實現上存在差異,而且可能干擾進程中現有的alarm調用。
(2)在select中阻塞等待I/O(select有內置的時間限制),以此替代直接阻塞在read或write調用上。
(3)使用SO_RECVTIMEO和SO_SNDTIMEO套接字選項
上述三個技術都適用於輸入輸出操作(例如read、write及其諸如recvfrom、sendto之類的變體),不過我們依然期待可用於connect的技術,因為TCP內置的connect超時相當長(典型值為75秒鍾)。select可用來在connect上設置超時的先決條件是相應的套接字是處於非阻塞模式,而那兩個套接字選項對connect並不適用。我們還指出,前兩個技術適用於任何描述符,而第三個技術僅僅適用於套接字描述符。

 

注:阻塞模式下,當服務器連接不上時,通過命令 “netstat -tlnap|grep SENT" 可以看到客戶端會處於SYN_SENT狀態,直到connect超時


二.非阻塞模式socket
1.connect不需要考慮超時問題,立即返回一個錯誤EINPROGRESS,可通過檢測fd是否可用判斷連接是否建立完成
2.read不需要考慮超時問題,立即返回
3.write不需要考慮超時問題,立即返回


三.阻塞模式socket
1.connect需要考慮超時問題,否則當連接IP不可達的情況下,需要等待很長一段時間(默認時長)
2.read需要考慮超時問題,可通過setsockopt設置SO_RECVTIMEO選項
3.write需要考慮超時問題,可通過setsockopt設置SO_SNDTIMEO選項

四.帶超時的connect,適用於阻塞模式與非阻塞模式socket(本質上是非阻塞connect)

 

附上測試代碼,可通過更改main中的IP和端口進行測試,讀懂這段代碼需要了解select與getsockopt函數

-----  g++ connect_nonb.cpp -----

 1 #include <sys/types.h>  
 2 #include <sys/time.h>  
 3 #include <sys/socket.h>  
 4 #include <netinet/in.h>    
 5 #include <arpa/inet.h>   
 6 #include <unistd.h>  
 7 #include <fcntl.h>  
 8 #include <stdio.h>  
 9 #include <errno.h>  
10 #include <stdlib.h>  
11 #include <string.h>  
12   
13 int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)  
14 {  
15     int flags, n, error;  
16     socklen_t len;  
17     fd_set rset, wset;  
18     struct timeval tval;  
19   
20     /* 調用fcntl把套接字設置為非阻塞 */  
21     flags = fcntl(sockfd, F_GETFL, 0);  
22     fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);  
23   
24     /* 發起非阻塞connect。期望的錯誤是EINPROGRESS,表示連接建立已經啟動但是尚未完成  */  
25     error = 0;  
26     if ( (n = connect(sockfd, addr, addrlen)) < 0)  
27         if (errno != EINPROGRESS)  
28             return(-1);  
29       
30     /* 如果非阻塞connect返回0,那么連接已經建立。當服務器處於客戶端所在主機時這種情況可能發生 */  
31     if (n == 0)  
32         goto done; /* connect completed immediately */  
33   
34     /* 調用select等待套接字變為可讀或可寫,如果select返回0,那么表示超時 */  
35     FD_ZERO(&rset);  
36     FD_SET(sockfd, &rset);  
37     wset = rset;  
38     tval.tv_sec = nsec;  
39     tval.tv_usec = 0;  
40   
41     if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {  
42         close(sockfd); /* timeout */  
43         errno = ETIMEDOUT;  
44         return(-1);  
45     }  
46   
47     /* 檢查可讀或可寫條件,調用getsockopt取得套接字的待處理錯誤,如果建立成功,該值將為0 */  
48     if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {  
49         len = sizeof(error);  
50         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)  
51             return(-1); /* Solaris pending error */  
52     } else {  
53         perror("select error: sockfd not set");  
54         exit(1);  
55     }  
56   
57     /* 恢復套接字的文件狀態標志並返回 */  
58 done:  
59     fcntl(sockfd, F_SETFL, flags); /* restore file status flags */  
60     if (error) {  
61         close(sockfd); /* just in case */  
62         errno = error;  
63         return(-1);  
64     }  
65     return(0);  
66 }  
67   
68 int main()  
69 {  
70     // socket    
71     struct sockaddr_in servaddr;      
72     short port = 9999;      
73     int sockfd = socket(AF_INET, SOCK_STREAM, 0);      
74     servaddr.sin_family = AF_INET;      
75     servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");      
76     servaddr.sin_port = htons(port);  
77     int timeout = 3;  
78       
79     if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) {    
80         perror("connect fail: ");  
81         return(-1);  
82     }  
83     printf("connect success!\n");  
84   
85     return 0;  
86 }  

 

在Linux下需要注意的是時間的控制結構是struct timeval而並不是某一整型數,須如下定義:

   struct timeval timeout = {3,0}; 

struct timeval {
time_t tv_sec; // seconds 
long tv_usec; // microseconds 
};
struct timeval有兩個成員,一個是秒,一個是微秒, 所以最高精確度是微秒。

//設置發送超時

setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval)); 

//設置接收超時

setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval)); 

有兩點注意就是: 
1)recv ()的第四個參數需為MSG_WAITALL,在阻塞模式下不等到指定數目的數據不會返回,除非超時時間到。還要注意的是只要設置了接收超時,在沒有 MSG_WAITALL時也是有效的。說到底超時就是不讓你的程序老在那兒等,到一定時間進行一次返回而已。 
2)即使等待超時時間值未到,但對方已經關閉了socket, 則此時recv()會立即返回,並收到多少數據返回多少數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM