目前各平台通用的設定通訊端(Socket)連線逾時的辦法是:
- 建立通訊端,將其設定成非阻塞狀態。
- 調用connect串連對端主機,如果失敗,判斷當時的errno是否為EINPROGRESS,也就是說是不是串連進行中中,如果是,轉到步驟3,如果不是,返回錯誤。
- 用select在指定的逾時時間內監聽通訊端的寫就緒事件,如果select有監聽到,證明串連成功,否則串連失敗。
以下是Linux環境下的範例程式碼:
#include <stdlib.h><br />#include <stdio.h><br />#include <unistd.h><br />#include <fcntl.h><br />#include <sys/types.h><br />#include <sys/socket.h><br />#include <netinet/in.h><br />#include <errno.h><br />#include <time.h><br />int main(int argc, char *argv[])<br />{<br /> int fd, retval;<br /> struct sockaddr_in addr;<br /> struct timeval timeo = {3, 0};<br /> socklen_t len = sizeof(timeo);<br /> fd_set set;<br /> fd = socket(AF_INET, SOCK_STREAM, 0);<br /> if (argc == 4)<br /> timeo.tv_sec = atoi(argv[3]);<br /> fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);<br /> addr.sin_family = AF_INET;<br /> addr.sin_addr.s_addr = inet_addr(argv[1]);<br /> addr.sin_port = htons(atoi(argv[2]));<br /> printf("%d/n", time(NULL));<br /> if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {<br /> printf("connected/n");<br /> return 0;<br /> }<br /> if (errno != EINPROGRESS) {<br /> perror("connect");<br /> return -1;<br /> }<br /> FD_ZERO(&set);<br /> FD_SET(fd, &set);<br /> retval = select(fd + 1, NULL, &set, NULL, &timeo);<br /> if (retval == -1) {<br /> perror("select");<br /> return -1;<br /> } else if(retval == 0) {<br /> fprintf(stderr, "timeout/n");<br /> printf("%d/n", time(NULL));<br /> return 0;<br /> }<br /> printf("connected/n");<br /> return 0;<br />}
實際運行結果如下:
xiaosuo@gentux perl $ ./a.out 10.16.101.1 90<br />1180289276<br />timeout<br />1180289279<br />xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 1<br />1180289281<br />timeout<br />1180289282
可以看到,以上代碼工作的很好,並且如果你想知道串連發生錯誤時的確切資訊的話,你可以用getsocketopt獲得:
int error;<br />socklen_t errorlen = sizeof(error);<br />getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorlen);
但是多少有些複雜,如果有象SO_SNDTIMO/SO_RCVTIMO一樣的通訊端參數可以讓逾時操作跳過select的話,世界將變得更美好。當然你還可以選用象apr一樣提供了簡單介面的庫,但我這裡要提的是另一種方法。
呵呵,引子似乎太長了點兒。讀Linux核心源碼的時候偶然發現其connect的逾時參數竟然和用SO_SNDTIMO操作的參數一致:
File: net/ipv4/af_inet.c
559 timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);<br /> 560<br /> 561 if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {<br /> 562 /* Error code is set above */<br /> 563 if (!timeo || !inet_wait_for_connect(sk, timeo))<br /> 564 goto out;<br /> 565<br /> 566 err = sock_intr_errno(timeo);<br /> 567 if (signal_pending(current))<br /> 568 goto out;<br /> 569 }
這意味著:在Linux平台下,可以通過在connect之前設定SO_SNDTIMO來達到控制連線逾時的目的。簡單的寫了份測試代碼:
#include <stdlib.h><br />#include <stdio.h><br />#include <sys/types.h><br />#include <sys/socket.h><br />#include <netinet/in.h><br />#include <errno.h><br />int main(int argc, char *argv[])<br />{<br /> int fd;<br /> struct sockaddr_in addr;<br /> struct timeval timeo = {3, 0};<br /> socklen_t len = sizeof(timeo);<br /> fd = socket(AF_INET, SOCK_STREAM, 0);<br /> if (argc == 4)<br /> timeo.tv_sec = atoi(argv[3]);<br /> setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);<br /> addr.sin_family = AF_INET;<br /> addr.sin_addr.s_addr = inet_addr(argv[1]);<br /> addr.sin_port = htons(atoi(argv[2]));<br /> if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {<br /> if (errno == EINPROGRESS) {<br /> fprintf(stderr, "timeout/n");<br /> return -1;<br /> }<br /> perror("connect");<br /> return 0;<br /> }<br /> printf("connected/n");<br /> return 0;<br />}
執行結果:
xiaosuo@gentux perl $ ./a.out 10.16.101.1 90<br />1180290583<br />timeout<br />1180290586<br />xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 2<br />1180290590<br />timeout<br />1180290592
和設想完全一致!