C++網絡編程快速入門(三):阻塞與非阻塞式調用網絡通信函數

目錄

  • 阻塞與非阻塞定義
  • send與recv
  • connect
  • 一些問題
    • 為什么要將監聽socket設置為非阻塞

阻塞與非阻塞定義

阻塞模式指的是當前某個函數執行效果未達預期,該函數會阻塞當前的執行線程,程序執行流在超時時間到達或者執行成功后恢復原有流程。非阻塞模式相反,即使某個函數執行結果未達預期,該函數也不會阻塞當前執行線程,而是立即返回。
網絡socket編程中,常見的connectacceptsendrecv函數均具有阻塞與非阻塞兩種調用方式。
阻塞與非阻塞socket具有各自適用的場景
非阻塞模式一般用于需要支持高并發QPS的場景,但是該模式會讓程序執行流和控制邏輯變復雜。
阻塞模式邏輯簡單,結構簡單。

send與recv

send函數本質不是向網絡發送數據,而是將應用層發送緩沖區的數據拷貝到內核緩沖區,至于數據什么時候從網卡緩沖區中真正的發到網絡中,要根據TCP/IP協議棧的行為來確定。
如果禁用nagel算法,存放到內核緩沖區的數據就會被立即發送出去。
否則如果一次放入緩沖區的數據包太小,系統會在多個小的數據包湊成一個足夠大的數據包后再發送。
反之,recv函數的本質則是將內核緩沖區的數據拷貝到應用緩沖區
而兩個程序進行網絡通信時,發送的一方會將內核緩沖區的數據通過網絡傳輸給接收方的內核緩沖區。這里的內核緩沖區也可以被稱為TCP窗口
在這里插入圖片描述
如果一端一直發送數據,對端應用一直不收取數據的話,則兩端的內核緩沖區很快會被填滿,導致調用send函數被阻塞(如果是阻塞模式下的話),從而影響當前線程的流程。如果是阻塞模式下德華,對端和本端的TCP窗口已滿,數據發送不出去,send函數會立即返回-1,并且得到EWOULDBLOCK的錯誤碼。
下面是非阻塞模式下send和recv函數的返回值總結

返回值返回值含義
大于0成功發送或者接受n個字節
0對端關閉連接
小于0出錯、信號被中斷、對端窗口太小導致數據發送不出去、當前網卡緩沖區無數據可接收

此時需要判斷返回值是否是我們期望的發送or接收的字節數。
如果對端的TCP窗口可能因為接收了部分數據就滿了,此時n的值就是(0,buf_length]了。
所以一般在循環中調用send函數,如果數據一次性發送不出去,則記錄偏移量,下一次從偏移量處接著發送,直到全部發送完為止:

bool sendData(int socketfd, const char* buf, int bufLength)
{// 已經發送的字節數int sentBytes = 0;int ret = 0;while (true) {ret = send(socketfd, buf + sentBytes, bufLength - sentBytes, 0);if (ret == -1) {if (errno == EWOULDBLOCK) {// 緩存尚未發送出去的數據,這里不具體寫// ... 緩存未發送出去的數據break;} else if (errno == EINTR) {continue;} else {return false;}} else if (ret == 0) {return false;}// 否則發送成功sentBytes += ret;if (sentBytes == bufLength)break;}return true;
}

當返回值為-1的時候我們需要根據不同的錯誤碼來進行對應處理:

錯誤碼send函數recv函數
EWOULDBLOCK 或者 EAGAINTCP窗口太小,數據暫時發送不出去當前內核緩沖區中無可讀數據
EINTR被信號中斷,需要重試被信號中斷,需要重試
不是以上兩種出錯出錯

connect

使用非阻塞的connect的步驟如下:
1、創建socket,將socket設置為非阻塞模式
2、調用connect函數,無論connect函數是否連接成功都立即返回;
3、調用select函數,在指定時間內判斷該socket是否可寫,若可寫,則說明連接成功,反之認為連接失敗。不過在linux系統上有些特殊:
connect之后,不僅要調用select檢測是否可寫,還要調用getsockpt檢測此時socket是否出錯,通過錯誤碼來檢測是否連接上,錯誤碼為0表示連接上。
在上一講中我們在服務端使用了select函數來監聽三種事件的發生,在客戶端也是可以用的。在這個問答中:select()可以用于客戶端,而不僅僅是服務器嗎?有這樣一個回答:
在客戶端套接字上使用select()的另一個好理由是跟蹤傳出的TCP連接進度。例如,這允許設置連接超時。 將客戶端套接字設置為非阻塞。 調用connect()。可能它會返回EINPROGRESS錯誤集(連接正在進行中,因為套接字是非阻塞的,所以不會被阻止)。 現在select()配置FD_SET以跟蹤客戶端套接字為’write-ready’。你也可以設置超時。 分析select()結果。 分析上次客戶端套接字操作是否失敗或成功。 最有用的是你可以在不同狀態的幾個套接字上使用它。因此,您可以真正無阻塞地處理多個套接字(客戶端,服務器,傳出,偵聽,接受…)。所有這一切只有一個線程。

代碼如下:

#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA "helloworld"using namespace std;int main() {// 創建一個socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1) {cout << " create client socket error " << endl;return -1;}// 將clientfd設置為非阻塞模式int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1) {close(clientfd);cout << "set socket to noblock error" << endl;return -1;}// 連接服務器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);// 此處與之前的阻塞式connect就不一樣了,需要用for循環,來輪詢狀態while (true) {int ret = connect(clientfd, (struct sockaddr *)& serveraddr, sizeof(serveraddr));if (ret == 0) {cout << "connect to server sucessfully" << endl;close(clientfd);return 0;} else if (ret == -1) {if (errno == EINTR) {// connect 被信號中斷了,重試connectcout << "connect interruptted by signal, try again" << endl;continue;} else if (errno == EINPROGRESS) {// 連接嘗試中break;} else {// 真的出錯了close(clientfd);return -1;}}}fd_set writeset;FD_ZERO(&writeset);FD_SET(clientfd, &writeset);struct timeval time;time.tv_sec = 3;time.tv_usec = 0;// 調用select判斷socket是否可寫if (select(clientfd + 1, NULL, &writeset, NULL, &time) != 1) {cout << "select connect to server error" << endl;close(clientfd);return -1;}int err;socklen_t len = static_cast<socklen_t>(sizeof err);// 調用getsockopt檢測此時socket是否出錯if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {close(clientfd);return -1;}if (err == 0) {cout << "connect to server successfully" <<endl;} else {cout << "connect to server error" << endl; }close(clientfd);return 0;
}

一些問題

為什么要將監聽socket設置為非阻塞

在第二講中我們談到select模型,常見的網絡通信模型都會使用IO多路復用技術如select、poll、epoll等。當有新的連接請求到來時,監聽套接字變為可讀,然后調用accept()接收新連接、返回一個連接套接字。
如果監聽套接字是阻塞的,問題可能出在什么地方?
根據TCP三次握手的示意圖:
在這里插入圖片描述
從圖中可知,connect()會先于accep()函數返回。
當一個連接到來的時候,監聽套接字可讀,此時,我們稍微等一段時間之后再調用accept()。就在這段時間內,客戶端設置linger選項(l_onoff = 1, l_linger = 0),然后調用了close(),那么客戶端將不經過四次揮手過程,通過發送RST報文斷開連接。服務端接收到RST報文,系統會將排隊的這個未完成連接直接刪除,此時就相當于沒有任何的連接請求到來, 而接著調用的accept()將會被阻塞,直到另外的新連接到來時才會返回。這是與IO多路復用的思想相違背的(系統不阻塞在某個具體的IO操作上,而是阻塞在select、poll、epoll這些IO復用上的)。
上述這種情況下,如果監聽套接字為非阻塞的,accept()不會阻塞住,立即返回-1,同時errno = EWOULDBLOCK

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/376812.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/376812.shtml
英文地址,請注明出處:http://en.pswp.cn/news/376812.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

css3實現頭像旋轉360度

css樣式: .div a img{ width: 88px; height: 88px; border-radius: 88px; transition: all 1.2s ease-out 0s;}.div a img:hover{ -webkit-transform:rotate(360deg); -moz-transform:rotate(360deg); -o-transform:rotate(360deg); -ms-transform:rotate(360deg); transform:r…

POJ 2488 深搜

POJ 2488 深搜 要求字典序的順序。 1 #include <iostream>2 #include <stdio.h>3 #include <string.h>4 using namespace std;5 int n,m,cnt;6 bool success;7 bool sign[30][30];8 int step[30][2];9 int dir[8][2]{ 10 -2,-1,-2,1, 11 …

socket 端口和地址復用

https://blog.csdn.net/weibo1230123/article/details/79978745 https://blog.csdn.net/weixin_42157432/article/details/115560824 在linux socket網絡編程中&#xff0c;大規模并發TCP或UDP連接時&#xff0c;經常會用到端口復用&#xff1a; int opt 1; if (setsockopt…

MyEclipse老是彈出problem occurred窗口

有的時候是因為jsp頁面中的java腳本有誤&#xff0c;比如說<% String name"";>就會出現錯誤&#xff0c;因為結束標簽少了一個百分號&#xff05;。轉載于:https://www.cnblogs.com/passer1991/archive/2013/03/15/2961624.html

C++網絡編程快速入門(四):EPOLL模型使用

目錄基本使用方法step1:創建epollfdstep2:將fd綁定到epollfdstep3:調用epoll_wait檢測事件epoll_wait與poll、select區別所在水平觸發與邊緣觸發基本使用方法 step1:創建epollfd 創建一個epollfd&#xff0c;若epoll_create調用成功&#xff0c;則返回一個非負值的epollfd&am…

Mysql中代替like模糊查詢的一種方法

使用Mysql的函數instr,可代替傳統的like方式查詢,并且速度更快。 instr函數&#xff0c;第一個參數是字段&#xff0c;第二個參數是要查詢的串&#xff0c;返回串的位置&#xff0c;第一個是1&#xff0c;如果沒找到就是0. 例如&#xff1a; select username from prefix_user …

兩種大小端判斷的方式

網絡通信是按照字節流進行數據交換的&#xff0c;主機根據不同的CPU型號可能是大段存儲&#xff0c;也可能是小端存儲。而網絡字節序在TCP/IP協議中已經規定好了&#xff0c;采用大端的排序方式。 所以網絡通信中一般將需要傳輸的整數型值轉換成網絡字節序。 從本機字節序轉換成…

把數據庫復制成腳本(包含遠程以及數據庫數據)

1.啟動VS 2.服務器資源管理器 3.連接需要的數據庫 4.右鍵數據庫 選擇publist to provider.... 5.剩下的 選擇數據庫 選擇存放地址 下一步 這方法應該是用在把08的數據還原到05上面 明天用這個方法去盜取哈公司的數據庫 看行不行轉載于:https://www.cnblogs.com/Rock-Lee/a…

代理模式用來初始化的延遲下載

package 設計模式; //代理模式實現延遲加載來減小啟動時間 //數據庫查詢接口 interface IDBQery{ public String request(); }class DBQuery implements IDBQery {//創建一個DBQery非常耗時的&#xff0c;這里面我可以在需要DBQuery的時候在創建public DBQuery(){try {Thread.s…

Linux網絡故障排查命令(ifconfig、ping、telnet、netstat、lsof、nc、curl、tcpdump)

目錄ifconfig-s&#xff0c;顯示網卡信息的精簡列表-a、up、down將IP地址綁定到某個網卡&#xff0c;以及解綁操作pingtelnetnetstatlsofnc模擬一個服務器程序和客戶端程序進行通信發送文件curltcpdump參數連接一個正常的監聽端口ifconfig 該命令用來查看當前系統的網卡和IP地…

My Oracle Support Metalink站點最近將放棄flash界面轉而使用ADF HTML

根據oracle官方博客的報道《The New My Oracle Support User Interface (HTML-based) 》&#xff0c; MY ORACLE SUPPORT開發team會在最近將support.oracle.com站點從原來的flash界面遷移到基于ADF HTML的用戶界面上。 實際上在2012年的 January 27&#xff0c; MOS開發team就…

BF算法

BF(Brute Force)算法是普通的模式匹配算法&#xff0c;BF算法的思想就是將目標串S的第一個字符與模式串T的第一個字符進行匹配&#xff0c;若相等&#xff0c;則繼續比較S的第二個字符和 T的第二個字符&#xff1b;若不相等&#xff0c;則比較S的第二個字符和T的第一個字符&…

心跳檢測以及應用層心跳包機制設計

博主聯系方式&#xff1a; QQ:1540984562 微信&#xff1a;wxid_nz49532kbh9u22 QQ交流群&#xff1a;750313950&#xff08;嵌入式方向&#xff09; QQ交流群&#xff1a;856398158&#xff08;后端方向&#xff09; 目錄心跳檢測應用場景死連接情況保活傳遞有效業務數據心跳包…

【APUE】孤兒進程與僵死進程

基本概念&#xff1a; 在unix/linux中&#xff0c;正常情況下&#xff0c;子進程是通過父進程創建的&#xff0c;子進程在創建新的進程。子進程的結束和父進程的運行是一個異步過程,即父進程永遠無法預測子進程 到底什么時候結束。 當一個 進程完成它的工作終止之后&#xff0c…

一個DBA的工作寫照

一個DBA的工作寫照&#xff0c; 一個DBA的內心 Know the DBA Mind! DBA也是 IT民工啊&#xff0c; 民工何苦為難民工&#xff01; 轉載于:https://www.cnblogs.com/macleanoracle/archive/2013/03/19/2968227.html

使用mutex和條件變量實現信號量

c提供了互斥量&#xff1a;mutex和條件變量&#xff1a;condition_variable&#xff0c;但是并沒有信號量&#xff1a;semaphore。而linux和windows系統庫會提供的。下面簡單介紹一下信號量的特性&#xff0c;然后給出一個簡單的demo&#xff0c;使用mutex condition_variable…

2014-07-28 使用Axure RP進行手機端BBS的原型設計

今天是在吾索實習的第14天。因本公司的微信公眾號需要有一個對外的技術交流平臺&#xff0c;所以我們小組打算設計一個手機端的BBS以滿足其要求。首先&#xff0c;我們需要做的是進行數據庫設計與原型設計&#xff0c;然后提交給經理驗收&#xff0c;看看是否合理&#xff0c;是…

jquery exif + lazyload實現延遲加載并顯示相片exif信息

對一個攝影愛好者來說&#xff0c;從高手的作品中學習是非常有用的。而照片的光圈&#xff0c;快門&#xff0c;感光度等信息是關注的重點。 上代碼&#xff1a; 1 <script src"../js/jquery.js" type"text/javascript"></script>2 <script…

undefined reference to `pthread_create‘(linux下Clion使用thread報錯)

完整報錯&#xff1a; [ Build | AsyncLogger | Debug ] /snap/clion/169/bin/cmake/linux/bin/cmake --build /home/dyy/CLionProjects/AsyncLogger/cmake-build-debug --target AsyncLogger -- -j 6 [ 50%] Linking CXX executable AsyncLogger /usr/bin/ld: CMakeFiles/Asy…

UVALive 6257 Chemist's vows --一道題的三種解法(模擬,DFS,DP)

題意&#xff1a;給一個元素周期表的元素符號&#xff08;114種&#xff09;&#xff0c;再給一個串&#xff0c;問這個串能否有這些元素符號組成&#xff08;全為小寫&#xff09;。 解法1&#xff1a;動態規劃 定義&#xff1a;dp[i]表示到 i 這個字符為止&#xff0c;能否有…