一、前言html
最近在寫一個測試工具,要求快速的高效率的掃描出各個服務器開放了哪些端口。當時想了一下,ping只能檢測ip,判斷服務器的網絡是連通的,而不能判斷是否開放了端口。咱們知道端口屬於網絡的傳輸層,所以須要用ip和端口來探測,這個時候就能夠用connect來探測一下,針對TCP協議,connect函數要進行TCP三次握手,若是connect成功,則說明服務器開放了某個端口,若是connect失敗,則說明服務器沒有開放某個端口。而connect失敗是經過超時來控制的,在規定的時間內,connect會發起屢次鏈接,一直執行到超時,才返回錯誤。默認狀況下,connect是阻塞的,並且默認的超時時間爲75s,正常狀況下,檢測網絡的連通性都是毫秒級,若是要判斷10萬臺服務器的,用阻塞的默認的connect去作,效率很是低下。所以採用非阻塞的connect,並且須要自定義超時間(我自定義超時時間爲5s)。linux
二、非阻塞connect服務器
對於阻塞式套接字,調用connect函數將激發TCP的三次握手過程,並且僅在鏈接創建成功或者出錯時才返回;對於非阻塞式套接字,若是調用connect函數會之間返回-1(表示出錯),且錯誤爲EINPROGRESS,表示鏈接創建,創建啓動可是還沒有完成;若是返回0,則表示鏈接已經創建,這一般是在服務器和客戶在同一臺主機上時發生。網絡
select是一種IO多路複用機制,它容許進程指示內核等待多個事件的任何一個發生,而且在有一個或者多個事件發生或者經歷一段指定的時間後才喚醒它。connect自己並不具備設置超時功能,若是想對套接字的IO操做設置超時,可以使用select函數。socket
對於select和非阻塞connect,注意兩點:[1] 當鏈接成功創建時,描述符變成可寫; [2] 當鏈接創建遇到錯誤時,描述符變爲便可讀,也可寫,遇到這種狀況,可調用getsockopt函數。函數
三、實現步驟工具
(1) 建立socket,並利用fcntl將其設置爲非阻塞測試
(2) 調用connect函數,若是返回0,則鏈接創建;若是返回-1,檢查errno ,若是值爲 EINPROGRESS,則鏈接正在創建。spa
(3) 爲了控制鏈接創建時間,將該socket描述符加入到select的可讀可寫集合中,採用select函數設定超時。code
(4) 若是規定時間內成功創建,則描述符變爲可寫;不然,採用getsockopt函數捕獲錯誤信息
(5) 恢復套接字的文件狀態並返回。
測試代碼以下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> /* See NOTES */ 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <fcntl.h> 9 #include <errno.h> 10 11 int main(int argc, char **argv) 12 { 13 if (argc < 3) { 14 printf("please input ip and port, for example ./main 120.12.34.56 80.\n"); 15 return -1; 16 } 17 18 19 char *ipaddr = argv[1]; 20 unsigned int port = atoi(argv[2]); 21 22 int fd = 0; 23 struct sockaddr_in addr; 24 fd_set fdr, fdw; 25 struct timeval timeout; 26 int err = 0; 27 int errlen = sizeof(err); 28 29 fd = socket(AF_INET,SOCK_STREAM,0); 30 if (fd < 0) { 31 fprintf(stderr, "create socket failed,error:%s.\n", strerror(errno)); 32 return -1; 33 } 34 35 bzero(&addr, sizeof(addr)); 36 addr.sin_family = AF_INET; 37 addr.sin_port = htons(port); 38 inet_pton(AF_INET, ipaddr, &addr.sin_addr); 39 40 /*設置套接字爲非阻塞*/ 41 int flags = fcntl(fd, F_GETFL, 0); 42 if (flags < 0) { 43 fprintf(stderr, "Get flags error:%s\n", strerror(errno)); 44 close(fd); 45 return -1; 46 } 47 flags |= O_NONBLOCK; 48 if (fcntl(fd, F_SETFL, flags) < 0) { 49 fprintf(stderr, "Set flags error:%s\n", strerror(errno)); 50 close(fd); 51 return -1; 52 } 53 54 /*阻塞狀況下linux系統默認超時時間爲75s*/ 55 int rc = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); 56 if (rc != 0) { 57 if (errno == EINPROGRESS) { 58 printf("Doing connection.\n"); 59 /*正在處理鏈接*/ 60 FD_ZERO(&fdr); 61 FD_ZERO(&fdw); 62 FD_SET(fd, &fdr); 63 FD_SET(fd, &fdw); 64 timeout.tv_sec = 10; 65 timeout.tv_usec = 0; 66 rc = select(fd + 1, &fdr, &fdw, NULL, &timeout); 67 printf("rc is: %d\n", rc); 68 /*select調用失敗*/ 69 if (rc < 0) { 70 fprintf(stderr, "connect error:%s\n", strerror(errno)); 71 close(fd); 72 return -1; 73 } 74 75 /*鏈接超時*/ 76 if (rc == 0) { 77 fprintf(stderr, "Connect timeout.\n"); 78 close(fd); 79 return -1; 80 } 81 /*[1] 當鏈接成功創建時,描述符變成可寫,rc=1*/ 82 if (rc == 1 && FD_ISSET(fd, &fdw)) { 83 printf("Connect success\n"); 84 close(fd); 85 return 0; 86 } 87 /*[2] 當鏈接創建遇到錯誤時,描述符變爲便可讀,也可寫,rc=2 遇到這種狀況,可調用getsockopt函數*/ 88 if (rc == 2) { 89 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { 90 fprintf(stderr, "getsockopt(SO_ERROR): %s", strerror(errno)); 91 close(fd); 92 return -1; 93 94 } 95 96 if (err) { 97 errno = err; 98 fprintf(stderr, "connect error:%s\n", strerror(errno)); 99 close(fd); 100 return -1; 101 102 } 103 } 104 105 } 106 fprintf(stderr, "connect failed, error:%s.\n", strerror(errno)); 107 return -1; 108 } 109 return 0; 110 }
四、參考資料
http://dongxicheng.org/network/non-block-connect-implemention/
http://www.cnblogs.com/flyxiang2010/archive/2010/12/17/1909051.html