自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何實現(xiàn)異步 Connect

開發(fā) 前端
寫過網(wǎng)絡程序的同學,應該都知道 connect 函數(shù),在 socket 開始讀寫操作之前,先要進行連接,也即 TCP 的三次握手 , 這個過程就是在 connect 函數(shù)中完成的, connect 函數(shù)本身是阻塞的,通過設置 socket 的選項及調(diào)用 select/poll 函數(shù)可以實現(xiàn)異步 connect 的功能。

[[402493]]

本文轉(zhuǎn)載自微信公眾號「 Linux開發(fā)那些事兒」,作者LinuxThings。轉(zhuǎn)載本文請聯(lián)系 Linux開發(fā)那些事兒公眾號。

寫過網(wǎng)絡程序的同學,應該都知道 connect 函數(shù),在 socket 開始讀寫操作之前,先要進行連接,也即 TCP 的三次握手 , 這個過程就是在 connect 函數(shù)中完成的, connect 函數(shù)本身是阻塞的,通過設置 socket 的選項及調(diào)用 select/poll 函數(shù)可以實現(xiàn)異步 connect 的功能

socket 默認是阻塞模式,處于阻塞模式時,調(diào)用 connect 函數(shù)之后, 會一直等待連接結(jié)果返回為止,要么成功,要么失敗,connect 函數(shù)返回 0 時成功,返回 -1 失敗

在局域網(wǎng)中,調(diào)用 connect 函數(shù),基本上會立即返回結(jié)果,當服務器在國外時,connect 函數(shù)時會阻塞一段時間,大概幾秒鐘吧,具體的還要看當時的網(wǎng)絡狀況

為什么要用異步 connect

Linux 下 connect 默認的超時時間大概在一分鐘左右(不同的Linux版本略有差別),在實際的開發(fā)中,這個時間顯得有點兒長了

對于服務器來說,需要為很多的客戶端服務,要盡量減少阻塞,所以,一般都是采用 異步 connect 的技術(shù)

對于每一個編寫網(wǎng)絡程序的同學來說,異步connect 應該是必須掌握的基本功

異步connect 步驟

(1) 創(chuàng)建socket,調(diào)用 fcntl 函數(shù)將其設置為非阻塞

(2) 調(diào)用 connect 函數(shù),返回 0 表示連接成功,返回 -1,需要檢查錯誤碼

如果錯誤碼為 EINPROGRESS,表示正在建立連接中

如果錯誤碼是 EINTR 表示,表示發(fā)生了系統(tǒng)中斷,這時繼續(xù)執(zhí)行連接即可

如果是其他錯誤碼,調(diào)用 close(fd) 函數(shù)關(guān)閉 socket, 連接失敗

(3) 將 socket 加入 select/poll 的可寫文件描述符集合中,并設置超時時間

(4) 判斷 select/poll 函數(shù)的返回值

小于等于 0 表示失敗

其他,表示 socket 可寫,調(diào)用 getsockopt 函數(shù) 捕獲 socket 的錯誤信息

具體的代碼如下:

  1. /* 
  2.     異步 connect 測試代碼, test_connect.cpp 
  3. */ 
  4. #include <stdint.h> 
  5. #include <sys/types.h> 
  6. #include <sys/socket.h> 
  7. #include <sys/select.h> 
  8. #include <poll.h> 
  9. #include <sys/un.h> 
  10. #include <netinet/in.h> 
  11. #include <netinet/tcp.h> 
  12. #include <arpa/inet.h> 
  13. #include <unistd.h> 
  14. #include <fcntl.h> 
  15. #include <string.h> 
  16. #include <netdb.h> 
  17. #include <errno.h> 
  18. #include <stdarg.h> 
  19. #include <poll.h> 
  20. #include <limits.h> 
  21. #include <iostream> 
  22. using namespace std; 
  23.  
  24. int32_t main(int32_t argc, char *argv[]) 
  25.     if(argc < 3) 
  26.     { 
  27.         std::cout << "argc < 3..." << std::endl; 
  28.         return -1; 
  29.     } 
  30.     std::string strip = argv[1]; 
  31.     uint32_t port = atoi(argv[2]); 
  32.     //創(chuàng)建 socket 
  33.     int32_t fd = socket(AF_INET, SOCK_STREAM, 0); 
  34.     if(-1 == fd) 
  35.     { 
  36.         std::cout << "create socket error:" << errno << std::endl; 
  37.         return -1; 
  38.     } 
  39.     //將 socket 設置成非阻塞 
  40.     int32_t flag = fcntl(fd, F_GETFL, 0); 
  41.     flag |= O_NONBLOCK; 
  42.     if(-1 == fcntl(fd, F_SETFL, flag)) 
  43.     { 
  44.         std::cout << " set socket nonblock error:" << errno << std::endl; 
  45.         close(fd); 
  46.         return -1; 
  47.     } 
  48.     //服務器地址 
  49.     struct sockaddr_in addr; 
  50.     addr.sin_family = AF_INET; 
  51.     addr.sin_port = htons(port); 
  52.     addr.sin_addr.s_addr = inet_addr(strip.c_str()); 
  53.     // 
  54.     for(; ;) 
  55.     { 
  56.         //連接服務器 
  57.         int32_t ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr) ); 
  58.         if(-1 == ret) 
  59.         { 
  60.             int32_t err = errno; 
  61.             if(EINTR == err) 
  62.             { 
  63.                 //connect被中斷,繼續(xù)重試 
  64.                 //如果不處理 EINTR 錯誤的話,connect邏輯可以不用放到 for 循環(huán)中 
  65.                 continue
  66.             } 
  67.             if(EINPROGRESS != err) 
  68.             { 
  69.                 std::cout << "connect err:" << errno << ", str:" << strerror(errno) <<  std::endl; 
  70.                 goto exit; 
  71.             } 
  72.             //正在連接中 
  73.             std::cout << "connecting..." << std::endl; 
  74.             //處理結(jié)果 
  75.             int32_t result = -1; 
  76.     #if 1 
  77.             //將 socket 加入到 poll 的可寫集合中 
  78.             struct pollfd wfd[1]; 
  79.             wfd[0].fd = fd; 
  80.             wfd[0].events = POLLOUT; 
  81.             //檢測 socket 是否可寫 
  82.             result = poll(wfd, 1, 3000); 
  83.     #elif 0 
  84.             //設置超時時間 
  85.             struct timeval tval; 
  86.             tval.tv_sec = 3; 
  87.             tval.tv_usec = 0; 
  88.             //將 socket 加入到 select 的可寫集合中 
  89.             fd_set wfds; 
  90.             FD_ZERO(&wfds); 
  91.             FD_SET(fd,&wfds); 
  92.             //檢測 socket 是否可寫 
  93.             result = select(fd + 1, nullptr, &wfds, nullptr,&tval); 
  94.     #endif 
  95.             std::cout << "async connect result:" << result << std::endl; 
  96.             // 失敗 
  97.             if(result <= 0 ) 
  98.             {  
  99.                 std::cout << "async connect err:" << errno << ", str:" << strerror(errno) << std::endl; 
  100.                 goto exit; 
  101.             } 
  102.             //檢查socket 錯誤信息 
  103.             int32_t temperr = 0; 
  104.             socklen_t temperrlen = sizeof(temperr); 
  105.             if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&temperr, &temperrlen) ) 
  106.             { 
  107.                 std::cout << "async connect...getsockopt err:" << errno << ", str:" << strerror(errno) <<  std::endl; 
  108.                 goto exit; 
  109.             } 
  110.             if(0 != temperr) 
  111.             { 
  112.                 std::cout << "async connect...getsockopt temperr:" << temperr << ", str:" << strerror(temperr) << std::endl; 
  113.                 goto exit; 
  114.             } 
  115.             //成功 
  116.             std::cout << "async connect success..." << std::endl; 
  117.             goto exit; 
  118.         } 
  119.         else 
  120.         { 
  121.              //連接成功 
  122.             std::cout << "connect success..." << std::endl; 
  123.             goto exit;           
  124.         } 
  125.     } // end of  for(; ;) 
  126. exit: 
  127.     std::cout << "quit...." << std::endl; 
  128.     close(fd); 
  129.     return 0; 
  • 代碼說明

如果不處理 EINTR 錯誤的話,connect 函數(shù)及后面的邏輯可以不用放到 for 循環(huán)中

檢查 socket 是否可寫,調(diào)用 select 或者 poll 函數(shù)都可以,上述代碼中使用的是 poll 函數(shù),將代碼中的 #if 1 改成 #if 0 以及 #elif 0 改成 #elif 1 , 就是使用 select 函數(shù)檢測 socket 是否可寫了

測試

在另一臺機器上執(zhí)行 nc -l -v -k 192.168.70.20 5000 命令,啟動一個服務器程序

在當前機器上執(zhí)行 g++ -g -Wall -std=c++11 -o test_connect test_connect.cpp 進行編譯

執(zhí)行 ./test_connect 192.168.70.20 5000, 結(jié)果如下圖

此時,服務器程序顯示如下:

通過 test_connect 程序端的截圖可以看出,調(diào)用 connect 函數(shù)之后,返回了 EINPROGRESS 錯誤碼,然后調(diào)用 select/poll 函數(shù)返回 1, 表示 socket 可寫,緊接著調(diào)用 getsockopt 函數(shù)檢查 socket 錯誤信息,通過打印的信息知道,socket 無錯誤信息,即 連接成功

我們在服務器機器上按 CTRL + C 停止服務器程序,然后關(guān)閉 test_connect 程序,再次執(zhí)行 ./test_connect 192.168.70.20 5000 ,結(jié)果如下圖:

從上圖可以看出,即使服務器程序已經(jīng)退出了,調(diào)用 select/poll 之后還是返回 socket 可寫,當繼續(xù)調(diào)用 getsockopt 函數(shù)檢查 socket 錯誤碼,此時錯誤碼是 111, 表示連接被拒絕,也即連接失敗

這里要注意一個很重要的點, 在 Linux 上,即使 socket 沒有連接成功,調(diào)用 select/poll 時,仍然返回 socket 是可寫的,所以 除了調(diào)用 select/poll 檢查 socket 可寫之外,還需要調(diào)用 getsockopt 函數(shù)檢查 socket 的錯誤碼,錯誤碼為 0 表示連接成功,其他表示連接失敗

 

責任編輯:武曉燕 來源: Linux開發(fā)那些事兒
相關(guān)推薦

2018-05-14 13:51:39

RDS Binlog架構(gòu)Kafka集群

2022-06-22 08:16:29

異步非阻塞框架

2023-08-02 08:03:08

Python線程池

2024-05-23 11:26:02

2013-05-21 13:33:02

Android游戲開發(fā)異步音樂播放

2023-03-10 14:56:37

Linuxconnect系統(tǒng)

2012-04-20 10:05:16

WCF

2017-05-11 20:20:59

JavascriptPromiseWeb

2024-12-26 12:59:39

2024-11-29 10:23:35

2024-12-10 00:00:30

ServletTomcat異步

2024-03-13 14:35:33

Spring事件異步

2011-02-24 12:53:51

.NET異步傳統(tǒng)

2013-06-27 11:16:27

Android異步加載

2024-08-13 09:26:07

2023-08-30 08:43:42

asyncioaiohttp

2021-02-09 09:51:58

異步傳遞數(shù)據(jù)

2012-12-28 14:32:34

Android開發(fā)Handler異步處理

2009-08-21 10:13:02

C#異步初步

2009-08-21 09:20:44

C#異步套接字
點贊
收藏

51CTO技術(shù)棧公眾號