linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
一、使用alarm 函數設置超時
?
C++ Code?| 1 2 3 4 5 6 7 8 9 10 11 12 13 | ? | void?handler( int?sig) { } signal(SIGALRM,?handler); alarm( 5); int?ret?=?read(fd,?buf,? sizeof(buf)); if?(ret?==?- 1?&&?errno?==?EINTR) ????errno?=?ETIMEOUT; else? if?(ret?>=? 0) ????alarm( 0); ................. |
?
?
程序大概框架如上所示,如果read在5s內被SIGALRM信號中斷而返回,則表示超時,否則未超時已讀取到數據,取消鬧鐘。但這種方法不常用,因為有時可能在其他地方使用了alarm會造成混亂。
二、使用套接字選項SO_SNDTIMEO、SO_RCVTIMEO
?
C++ Code?| 1 2 3 4 5 6 | ? | setsockopt(sock,?SOL_SOCKET,?SO_RCVTIMEO,? 5); int?ret?=?read(sock,?buf,? sizeof(buf)); if?(ret?==?- 1?&&?errno?==?EWOULDBLOCK) ????errno?=?ETIMEOUT; .......... |
?
?
即使用setsockopt 函數進行設置,但這種方法可移植性比較差,不是每種系統實現都有這些選項。
三、使用select 實現超時
下面程序包含read_timeout、write_timeout、accept_timeout、connect_timeout 四個函數封裝
?
C++ Code?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | ? | /************************************************************************* ????>?File?Name:?sysutil.c ????>?Author:?Simba ????>?Mail:?dameng34@163.com ????>?Created?Time:?Sat?02?Mar?2013?10:53:06?PM?CST ?************************************************************************/ #include? "sysutil.h" /*?read_timeout?-?讀超時檢測函數,不含讀操作 ?*?fd:文件描述符 ?*?wait_seconds:等待超時秒數,?如果為0表示不檢測超時; ?*?成功(未超時)返回0,失敗返回-1,超時返回-1并且errno?=?ETIMEDOUT ?*/ int?read_timeout( int?fd,? unsigned? int?wait_seconds) { ???? int?ret?=? 0; ???? if?(wait_seconds?>? 0) ????{ ????????fd_set?read_fdset; ???????? struct?timeval?timeout; ????????FD_ZERO(&read_fdset); ????????FD_SET(fd,?&read_fdset); ????????timeout.tv_sec?=?wait_seconds; ????????timeout.tv_usec?=? 0; ???????? do ????????{ ????????????ret?=?select(fd?+? 1,?&read_fdset,? NULL,? NULL,?&timeout);? //select會阻塞直到檢測到事件或者超時 ???????????? //?如果select檢測到可讀事件發送,則此時調用read不會阻塞 ????????} ???????? while?(ret?<? 0?&&?errno?==?EINTR); ???????? if?(ret?==? 0) ????????{ ????????????ret?=?- 1; ????????????errno?=?ETIMEDOUT; ????????} ???????? else? if?(ret?==? 1) ???????????? return? 0; ????} ???? return?ret; } /*?write_timeout?-?寫超時檢測函數,不含寫操作 ?*?fd:文件描述符 ?*?wait_seconds:等待超時秒數,?如果為0表示不檢測超時; ?*?成功(未超時)返回0,失敗返回-1,超時返回-1并且errno?=?ETIMEDOUT ?*/ int?write_timeout( int?fd,? unsigned? int?wait_seconds) { ???? int?ret?=? 0; ???? if?(wait_seconds?>? 0) ????{ ????????fd_set?write_fdset; ???????? struct?timeval?timeout; ????????FD_ZERO(&write_fdset); ????????FD_SET(fd,?&write_fdset); ????????timeout.tv_sec?=?wait_seconds; ????????timeout.tv_usec?=? 0; ???????? do ????????{ ????????????ret?=?select(fd?+? 1,? NULL,?&write_fdset,? NULL,?&timeout); ????????} ???????? while?(ret?<? 0?&&?errno?==?EINTR); ???????? if?(ret?==? 0) ????????{ ????????????ret?=?- 1; ????????????errno?=?ETIMEDOUT; ????????} ???????? else? if?(ret?==? 1) ???????????? return? 0; ????} ???? return?ret; } /*?accept_timeout?-?帶超時的accept ?*?fd:?套接字 ?*?addr:?輸出參數,返回對方地址 ?*?wait_seconds:?等待超時秒數,如果為0表示正常模式 ?*?成功(未超時)返回已連接套接字,失敗返回-1,超時返回-1并且errno?=?ETIMEDOUT ?*/ int?accept_timeout( int?fd,? struct?sockaddr_in?*addr,? unsigned? int?wait_seconds) { ???? int?ret; ????socklen_t?addrlen?=? sizeof( struct?sockaddr_in); ???? if?(wait_seconds?>? 0) ????{ ????????fd_set?accept_fdset; ???????? struct?timeval?timeout; ????????FD_ZERO(&accept_fdset); ????????FD_SET(fd,?&accept_fdset); ????????timeout.tv_sec?=?wait_seconds; ????????timeout.tv_usec?=? 0; ???????? do ????????{ ????????????ret?=?select(fd?+? 1,?&accept_fdset,? NULL,? NULL,?&timeout); ????????} ???????? while?(ret?<? 0?&&?errno?==?EINTR); ???????? if?(ret?==?- 1) ???????????? return?- 1; ???????? else? if?(ret?==? 0) ????????{ ????????????errno?=?ETIMEDOUT; ???????????? return?- 1; ????????} ????} ???? if?(addr?!=? NULL) ????????ret?=?accept(fd,?( struct?sockaddr?*)addr,?&addrlen); ???? else ????????ret?=?accept(fd,? NULL,? NULL); ???? if?(ret?==?- 1) ????????ERR_EXIT( "accpet?error"); ???? return?ret; } /*?activate_nonblock?-?設置IO為非阻塞模式 ?*?fd:?文件描述符 ?*/ void?activate_nonblock( int?fd) { ???? int?ret; ???? int?flags?=?fcntl(fd,?F_GETFL); ???? if?(flags?==?- 1) ????????ERR_EXIT( "fcntl?error"); ????flags?|=?O_NONBLOCK; ????ret?=?fcntl(fd,?F_SETFL,?flags); ???? if?(ret?==?- 1) ????????ERR_EXIT( "fcntl?error"); } /*?deactivate_nonblock?-?設置IO為阻塞模式 ?*?fd:?文件描述符 ?*/ void?deactivate_nonblock( int?fd) { ???? int?ret; ???? int?flags?=?fcntl(fd,?F_GETFL); ???? if?(flags?==?- 1) ????????ERR_EXIT( "fcntl?error"); ????flags?&=?~O_NONBLOCK; ????ret?=?fcntl(fd,?F_SETFL,?flags); ???? if?(ret?==?- 1) ????????ERR_EXIT( "fcntl?error"); } /*?connect_timeout?-?帶超時的connect ?*?fd:?套接字 ?*?addr:?輸出參數,返回對方地址 ?*?wait_seconds:?等待超時秒數,如果為0表示正常模式 ?*?成功(未超時)返回0,失敗返回-1,超時返回-1并且errno?=?ETIMEDOUT ?*/ int?connect_timeout( int?fd,? struct?sockaddr_in?*addr,? unsigned? int?wait_seconds) { ???? int?ret; ????socklen_t?addrlen?=? sizeof( struct?sockaddr_in); ???? if?(wait_seconds?>? 0) ????????activate_nonblock(fd); ????ret?=?connect(fd,?( struct?sockaddr?*)addr,?addrlen); ???? if?(ret?<? 0?&&?errno?==?EINPROGRESS) ????{ ????????fd_set?connect_fdset; ???????? struct?timeval?timeout; ????????FD_ZERO(&connect_fdset); ????????FD_SET(fd,?&connect_fdset); ????????timeout.tv_sec?=?wait_seconds; ????????timeout.tv_usec?=? 0; ???????? do ????????{ ???????????? /*?一旦連接建立,套接字就可寫?*/ ????????????ret?=?select(fd?+? 1,? NULL,?&connect_fdset,? NULL,?&timeout); ????????} ???????? while?(ret?<? 0?&&?errno?==?EINTR); ???????? if?(ret?==? 0) ????????{ ????????????errno?=?ETIMEDOUT; ???????????? return?- 1; ????????} ???????? else? if?(ret?<? 0) ???????????? return?- 1; ???????? else? if?(ret?==? 1) ????????{ ???????????? /*?ret返回為1,可能有兩種情況,一種是連接建立成功,一種是套接字產生錯誤 ?????????????*?此時錯誤信息不會保存至errno變量中(select沒出錯),因此,需要調用 ?????????????*?getsockopt來獲取?*/ ???????????? int?err; ????????????socklen_t?socklen?=? sizeof(err); ???????????? int?sockoptret?=?getsockopt(fd,?SOL_SOCKET,?SO_ERROR,?&err,?&socklen); ???????????? if?(sockoptret?==?- 1) ???????????????? return?- 1; ???????????? if?(err?==? 0) ????????????????ret?=? 0; ???????????? else ????????????{ ????????????????errno?=?err; ????????????????ret?=?- 1; ????????????} ????????} ????} ???? if?(wait_seconds?>? 0) ????????deactivate_nonblock(fd); ???? return?ret; } |
1、read_timeout :如注釋所寫,這只是讀超時檢測函數,并不包含讀操作,如果從此函數成功返回,則此時調用read將不再阻塞,測試代碼可以這樣寫: C++ Code?
| 1 2 3 4 5 6 7 8 | ? | int?ret; ret?=?read_timeout(fd,? 5); if?(ret?==? 0) ????read(fd,?buf,? sizeof(buf)); else? if?(ret?==?- 1?&&?errno?==?ETIMEOUT) ????printf( "timeout...\n"); else ????ERR_EXIT( "read_timeout"); |
?
如果 read_timeout(fd, 0); 則表示不檢測超時,函數直接返回為0,此時再調用read 將會阻塞。
當wait_seconds 參數大于0,則進入if 括號執行,將超時時間設置為select函數的超時時間結構體,select會阻塞直到檢測到事件發生或者超時。如果select返回-1且errno 為EINTR,說明是被信號中斷,需要重啟select;如果select返回0表示超時;如果select返回1表示檢測到可讀事件;否則select返回-1 表示出錯。
2、write_timeout :此函數跟read_timeout 函數類似,只是select 關心的是可寫事件,不再贅述。
3、accept_timeout :此函數是帶超時的accept 函數,如果能從if (wait_seconds > 0) 括號執行后向下執行,說明select 返回為1,檢測到已連接隊列不為空,此時再調用accept 不再阻塞,當然如果wait_seconds == 0 則像正常模式一樣,accept 阻塞等待,注意,accept 返回的是已連接套接字。
4、connect_timeout :在調用connect前需要使用fcntl 函數將套接字標志設置為非阻塞,如果網絡環境很好,則connect立即返回0,不進入if 大括號執行;如果網絡環境擁塞,則connect返回-1且errno == EINPROGRESS,表示正在處理。此后調用select與前面3個函數類似,但這里關注的是可寫事件,因為一旦連接建立,套接字就可寫。還需要注意的是當select 返回1,可能有兩種情況,一種是連接成功,一種是套接字產生錯誤,由這里可知,這兩種情況都會產生可寫事件,所以需要使用getsockopt來獲取一下。退出之前還需重新將套接字設置為阻塞。
我們可以寫個小程序測試一下connect_timeout 函數,客戶端程序如下:
?
C++ Code?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ? | #include? "sysutil.h" int?main( void) { ???? int?sock; ???? if?((sock?=?socket(PF_INET,?SOCK_STREAM,?IPPROTO_TCP))?<? 0) ????????ERR_EXIT( "socket"); ???? struct?sockaddr_in?servaddr; ????memset(&servaddr,? 0,? sizeof(servaddr)); ????servaddr.sin_family?=?AF_INET; ????servaddr.sin_port?=?htons( 5188); ????servaddr.sin_addr.s_addr?=?inet_addr( "127.0.0.1"); ???? int?ret?=?connect_timeout(sock,?&servaddr,? 5); ???? if?(ret?==?- 1?&&?errno?==?ETIMEDOUT) ????{ ????????printf( "timeout...\n"); ???????? return? 1; ????} ???? else? if?(ret?==?- 1) ????????ERR_EXIT( "connect_timeout"); ???? struct?sockaddr_in?localaddr; ????socklen_t?addrlen?=? sizeof(localaddr); ???? if?(getsockname(sock,?( struct?sockaddr?*)&localaddr,?&addrlen)?<? 0) ????????ERR_EXIT( "getsockname"); ????printf( "ip=%s?port=%d\n",?inet_ntoa(localaddr.sin_addr),?ntohs(localaddr.sin_port)); ???? return? 0; } |
?
?
因為是在本機上測試,所以不會出現超時的情況,但出錯的情況還是可以看到的,比如不要啟動服務器端程序,而直接啟動客戶端程序,輸出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_timeout?
connect_timeout: Connection refused
很明顯是connect_timeout 函數返回了-1,我們也可以推算出connect_timeout 函數中,select返回1,但卻是套接字發生錯誤的情況,errno = ECONNREFUSED,所以打印出Connection refused。
?
參考:
《Linux C 編程一站式學習》
《TCP/IP詳解 卷一》
《UNP》
轉載于:https://www.cnblogs.com/snake-hand/archive/2013/06/11/3132321.html
總結
以上是生活随笔為你收集整理的linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js 调 CDHtmlDialog AP
- 下一篇: Oracle 11g服务器与客户端卸载、