新聞中心
本文轉(zhuǎn)載自微信公眾號(hào)「 Linux開發(fā)那些事兒」,作者LinuxThings。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Linux開發(fā)那些事兒公眾號(hào)。

堅(jiān)守“ 做人真誠(chéng) · 做事靠譜 · 口碑至上 · 高效敬業(yè) ”的價(jià)值觀,專業(yè)網(wǎng)站建設(shè)服務(wù)10余年為成都木制涼亭小微創(chuàng)業(yè)公司專業(yè)提供企業(yè)網(wǎng)站建設(shè)營(yíng)銷網(wǎng)站建設(shè)商城網(wǎng)站建設(shè)手機(jī)網(wǎng)站建設(shè)小程序網(wǎng)站建設(shè)網(wǎng)站改版,從內(nèi)容策劃、視覺設(shè)計(jì)、底層架構(gòu)、網(wǎng)頁布局、功能開發(fā)迭代于一體的高端網(wǎng)站建設(shè)服務(wù)。
寫過網(wǎng)絡(luò)程序的同學(xué),應(yīng)該都知道 connect 函數(shù),在 socket 開始讀寫操作之前,先要進(jìn)行連接,也即 TCP 的三次握手 , 這個(gè)過程就是在 connect 函數(shù)中完成的, connect 函數(shù)本身是阻塞的,通過設(shè)置 socket 的選項(xiàng)及調(diào)用 select/poll 函數(shù)可以實(shí)現(xiàn)異步 connect 的功能
socket 默認(rèn)是阻塞模式,處于阻塞模式時(shí),調(diào)用 connect 函數(shù)之后, 會(huì)一直等待連接結(jié)果返回為止,要么成功,要么失敗,connect 函數(shù)返回 0 時(shí)成功,返回 -1 失敗
在局域網(wǎng)中,調(diào)用 connect 函數(shù),基本上會(huì)立即返回結(jié)果,當(dāng)服務(wù)器在國(guó)外時(shí),connect 函數(shù)時(shí)會(huì)阻塞一段時(shí)間,大概幾秒鐘吧,具體的還要看當(dāng)時(shí)的網(wǎng)絡(luò)狀況
為什么要用異步 connect
Linux 下 connect 默認(rèn)的超時(shí)時(shí)間大概在一分鐘左右(不同的Linux版本略有差別),在實(shí)際的開發(fā)中,這個(gè)時(shí)間顯得有點(diǎn)兒長(zhǎng)了
對(duì)于服務(wù)器來說,需要為很多的客戶端服務(wù),要盡量減少阻塞,所以,一般都是采用 異步 connect 的技術(shù)
對(duì)于每一個(gè)編寫網(wǎng)絡(luò)程序的同學(xué)來說,異步connect 應(yīng)該是必須掌握的基本功
異步connect 步驟
(1) 創(chuàng)建socket,調(diào)用 fcntl 函數(shù)將其設(shè)置為非阻塞
(2) 調(diào)用 connect 函數(shù),返回 0 表示連接成功,返回 -1,需要檢查錯(cuò)誤碼
如果錯(cuò)誤碼為 EINPROGRESS,表示正在建立連接中
如果錯(cuò)誤碼是 EINTR 表示,表示發(fā)生了系統(tǒng)中斷,這時(shí)繼續(xù)執(zhí)行連接即可
如果是其他錯(cuò)誤碼,調(diào)用 close(fd) 函數(shù)關(guān)閉 socket, 連接失敗
(3) 將 socket 加入 select/poll 的可寫文件描述符集合中,并設(shè)置超時(shí)時(shí)間
(4) 判斷 select/poll 函數(shù)的返回值
小于等于 0 表示失敗
其他,表示 socket 可寫,調(diào)用 getsockopt 函數(shù) 捕獲 socket 的錯(cuò)誤信息
具體的代碼如下:
- /*
- 異步 connect 測(cè)試代碼, test_connect.cpp
- */
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
- int32_t main(int32_t argc, char *argv[])
- {
- if(argc < 3)
- {
- std::cout << "argc < 3..." << std::endl;
- return -1;
- }
- std::string strip = argv[1];
- uint32_t port = atoi(argv[2]);
- //創(chuàng)建 socket
- int32_t fd = socket(AF_INET, SOCK_STREAM, 0);
- if(-1 == fd)
- {
- std::cout << "create socket error:" << errno << std::endl;
- return -1;
- }
- //將 socket 設(shè)置成非阻塞
- int32_t flag = fcntl(fd, F_GETFL, 0);
- flag |= O_NONBLOCK;
- if(-1 == fcntl(fd, F_SETFL, flag))
- {
- std::cout << " set socket nonblock error:" << errno << std::endl;
- close(fd);
- return -1;
- }
- //服務(wù)器地址
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = inet_addr(strip.c_str());
- //
- for(; ;)
- {
- //連接服務(wù)器
- int32_t ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr) );
- if(-1 == ret)
- {
- int32_t err = errno;
- if(EINTR == err)
- {
- //connect被中斷,繼續(xù)重試
- //如果不處理 EINTR 錯(cuò)誤的話,connect邏輯可以不用放到 for 循環(huán)中
- continue;
- }
- if(EINPROGRESS != err)
- {
- std::cout << "connect err:" << errno << ", str:" << strerror(errno) << std::endl;
- goto exit;
- }
- //正在連接中
- std::cout << "connecting..." << std::endl;
- //處理結(jié)果
- int32_t result = -1;
- #if 1
- //將 socket 加入到 poll 的可寫集合中
- struct pollfd wfd[1];
- wfd[0].fd = fd;
- wfd[0].events = POLLOUT;
- //檢測(cè) socket 是否可寫
- result = poll(wfd, 1, 3000);
- #elif 0
- //設(shè)置超時(shí)時(shí)間
- struct timeval tval;
- tval.tv_sec = 3;
- tval.tv_usec = 0;
- //將 socket 加入到 select 的可寫集合中
- fd_set wfds;
- FD_ZERO(&wfds);
- FD_SET(fd,&wfds);
- //檢測(cè) socket 是否可寫
- result = select(fd + 1, nullptr, &wfds, nullptr,&tval);
- #endif
- std::cout << "async connect result:" << result << std::endl;
- // 失敗
- if(result <= 0 )
- {
- std::cout << "async connect err:" << errno << ", str:" << strerror(errno) << std::endl;
- goto exit;
- }
- //檢查socket 錯(cuò)誤信息
- int32_t temperr = 0;
- socklen_t temperrlen = sizeof(temperr);
- if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&temperr, &temperrlen) )
- {
- std::cout << "async connect...getsockopt err:" << errno << ", str:" << strerror(errno) << std::endl;
- goto exit;
- }
- if(0 != temperr)
- {
- std::cout << "async connect...getsockopt temperr:" << temperr << ", str:" << strerror(temperr) << std::endl;
- goto exit;
- }
- //成功
- std::cout << "async connect success..." << std::endl;
- goto exit;
- }
- else
- {
- //連接成功
- std::cout << "connect success..." << std::endl;
- goto exit;
- }
- } // end of for(; ;)
- exit:
- std::cout << "quit...." << std::endl;
- close(fd);
- return 0;
- }
- 代碼說明
如果不處理 EINTR 錯(cuò)誤的話,connect 函數(shù)及后面的邏輯可以不用放到 for 循環(huán)中
檢查 socket 是否可寫,調(diào)用 select 或者 poll 函數(shù)都可以,上述代碼中使用的是 poll 函數(shù),將代碼中的 #if 1 改成 #if 0 以及 #elif 0 改成 #elif 1 , 就是使用 select 函數(shù)檢測(cè) socket 是否可寫了
測(cè)試
在另一臺(tái)機(jī)器上執(zhí)行 nc -l -v -k 192.168.70.20 5000 命令,啟動(dòng)一個(gè)服務(wù)器程序
在當(dāng)前機(jī)器上執(zhí)行 g++ -g -Wall -std=c++11 -o test_connect test_connect.cpp 進(jìn)行編譯
執(zhí)行 ./test_connect 192.168.70.20 5000, 結(jié)果如下圖
此時(shí),服務(wù)器程序顯示如下:
通過 test_connect 程序端的截圖可以看出,調(diào)用 connect 函數(shù)之后,返回了 EINPROGRESS 錯(cuò)誤碼,然后調(diào)用 select/poll 函數(shù)返回 1, 表示 socket 可寫,緊接著調(diào)用 getsockopt 函數(shù)檢查 socket 錯(cuò)誤信息,通過打印的信息知道,socket 無錯(cuò)誤信息,即 連接成功
我們?cè)诜?wù)器機(jī)器上按 CTRL + C 停止服務(wù)器程序,然后關(guān)閉 test_connect 程序,再次執(zhí)行 ./test_connect 192.168.70.20 5000 ,結(jié)果如下圖:
從上圖可以看出,即使服務(wù)器程序已經(jīng)退出了,調(diào)用 select/poll 之后還是返回 socket 可寫,當(dāng)繼續(xù)調(diào)用 getsockopt 函數(shù)檢查 socket 錯(cuò)誤碼,此時(shí)錯(cuò)誤碼是 111, 表示連接被拒絕,也即連接失敗
這里要注意一個(gè)很重要的點(diǎn), 在 Linux 上,即使 socket 沒有連接成功,調(diào)用 select/poll 時(shí),仍然返回 socket 是可寫的,所以 除了調(diào)用 select/poll 檢查 socket 可寫之外,還需要調(diào)用 getsockopt 函數(shù)檢查 socket 的錯(cuò)誤碼,錯(cuò)誤碼為 0 表示連接成功,其他表示連接失敗
網(wǎng)頁題目:如何實(shí)現(xiàn)異步 Connect
瀏覽地址:http://fisionsoft.com.cn/article/dhjgdis.html


咨詢
建站咨詢
