對于linux socket與epoll配合相關的一些心得記錄

對于linux socket與epoll配合相關的一些心得記錄

沒有多少高深的東西,全當記錄,雖然簡單,但是沒有做過測試還是挺容易讓人糊涂的
int nRecvBuf=32*1024;//設置為32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
1、通過上面語句可以簡單設置緩沖區大小,測試證明:跟epoll結合的時候只有當單次發送的數據全被從緩沖區讀完畢之后才會再次被觸發,多次發送數據如果沒有讀取完畢當緩沖區未滿的時候數據不會丟失,會累加到后面。
2、? ?如果緩沖區未滿,同一連接多次發送數據會多次收到EPOLLIN事件。
單次發送數據>socket緩沖區大小的數據數據會被阻塞分次發送,所以循環接收可以用ENLIGE錯誤判斷。
3、如果緩沖區滿,新發送的數據不會觸發epoll事件(也無異常),每次recv都會為緩沖區騰出空間,只有當緩沖區空閑大小能夠再次接收數據epollIN事件可以再次被觸發
接收時接收大小為0表示客戶端斷開(不可能有0數據包觸發EPOLLIN),-1表示異常,針對errorno進行判斷可以確定是合理異常還是需要終止的異常,>0而不等于緩沖區大小表示單次發送結束。
4、 如果中途臨時調整接收緩存區大小,并且在上一次中數據沒有完全接收到用戶空間,數據不會丟失,會累加在一起
所以總結起來,系統對于數據的完整性還是做了相當的保正,至于穩定性沒有作更深一步的測試
新增加:
5、如果主accept監聽的soctet
fd也設置為非阻塞,那么單純靠epoll事件來驅動的服務器模型會存在問題,并發壓力下發現,每次accept只從系統中取得第一個,所以如果恰馮多個
連接同時觸發server
fd的EPOLLIN事件,在返回的event數組中體現不出來,會出現丟失事件的現象,所以當用ab等工具簡單的壓載就會發現每次都會有最后幾條信息得
不到處理,原因就在于此,我現在的解決辦法是將server fd的監聽去掉,用一個線程阻塞監聽,accept成功就處理檢測client
fd,然后在主線程循環監聽client事件,這樣epoll在邊緣模式下出錯的概率就小,測試表明效果明顯
6、對于SIG部分信號還是要做屏蔽處理,不然對方socket中斷等正常事件都會引起整個服務的退出
7、sendfile(fd, f->SL->sendBuffer.inFd, (off_t
*)&f->SL->sendBuffer.offset,
size_need);注意sendfile函數的地三個變量是傳送地址,偏移量會自動增加,不需要手動再次增加,否則就會出現文件傳送丟失現象
8、單線程epoll驅動模型誤解:以前我一直認為單線程是無法處理web服務器這樣的有嚴重網絡延遲的服務,但nginx等優秀服務器都是機遇事件驅動
模型,開始我在些的時候也是擔心這些問題,后來測試發現,當client
socket設為非阻塞模式的時候,從讀取數據到解析http協議,到發送數據均在epoll的驅動下速度非常快,沒有必要采用多線程,我的單核cpu
(奔三)就可以達到10000page/second,這在公網上是遠遠無法達到的一個數字(網絡延遲更為嚴重),所以單線程的數據處理能力已經很高了,
就不需要多線程了,所不同的是你在架構服務器的時候需要將所有阻塞的部分拆分開來,當epoll通知你可以讀取的時候,實際上部分數據已經到了
socket緩沖區,你所讀取用的事件是將數據從內核空間拷貝到用戶空間,同理,寫也是一樣的,所以epoll重要的地方就是將這兩個延時的部分做了類似
的異步處理,如果不需要處理更為復雜的業務,那單線程足以滿足1000M網卡的最高要求,這才是單線程的意義。

我以前構建的web服務器就沒有理解epoll,采用epoll的邊緣觸發之后怕事件丟失,或者單線程處理阻塞,所以自己用多線程構建了一個任務調度器,
所有收到的事件統統壓進任無調度器中,然后多任務處理,我還將read和write分別用兩個調度器處理,并打算如果中間需要特殊的耗時的處理就增加一套
調度器,用少量線程+epoll的方法來題高性能,后來發現read和write部分調度器是多余的,epoll本來就是一個事件調度器,在后面再次緩存
事件分部處理還不如將epoll設為水平模式,所以多此一舉,但是這個調度起還是有用處的

上面講到如果中間有耗時的工作,比如數據庫讀寫,外部資源請求(文件,socket)等這些操作就不能阻塞在主線程里面,所以我設計的這個任務調度器就有
用了,在epoll能處理的事件驅動部分就借用epoll的,中間部分采用模塊化的設計,用函數指針達到面相對象語言中的“委托”的作用,就可以滿足不同
的需要將任務(fd標識)加入調度器,讓多線程循環執行,如果中間再次遇到阻塞就會再次加入自定義的阻塞器,檢測完成就加入再次存入調度器,這樣就可以將
多種復雜的任務劃分開來,相當于在處理的中間環節在自己購置一個類似于epoll的事件驅動器

9、多系統兼容:我現在倒是覺得與其構建一個多操作系統都支持的服務器不如構建特定系統的,如果想遷移再次改動,因為一旦兼顧到多個系統的化會大大增加系
統的復雜度,并且不能最優性能,每個系統都有自己的獨有的優化選項,所以我覺得遷移的工作量遠遠小于兼顧的工作量
10模塊化編程,雖然用c還是要講求一些模塊化的設計的,我現在才發現幾乎面相對想的語言所能實現的所有高級特性在c里面幾乎都有對應的解決辦法(暫時發現除了操作符重載),所有學過高級面相對象的語言的朋友不放把模式用c來實現,也是一種樂趣,便于維護和自己閱讀
11、養成注釋的好習慣
SO_RCVBUF SO_SNDBUF
先明確一個概念:每個TCP socket在內核中都有一個發送緩沖區和一個接收緩沖區,TCP的全雙工的工作模式以及TCP的滑動窗口便是依賴于這兩個獨立的buffer以及此buffer的填充狀態。接收緩沖區把數據緩存入內核,應用進程一直沒有調用read進行讀取的話,此數據會一直緩存在相應socket的接收緩沖區內。再啰嗦一點,不管進程是否讀取socket,對端發來的數據都會經由內核接收并且緩存到socket的內核接收緩沖區之中。read所做的工作,就是把內核緩沖區中的數據拷貝到應用層用戶的buffer里面,僅此而已。進程調用send發送的數據的時候,最簡單情況(也是一般情況),將數據拷貝進入socket的內核發送緩沖區之中,然后send便會在上層返回。換句話說,send返回之時,數據不一定會發送到對端去(和write寫文件有點類似),send僅僅是把應用層buffer的數據拷貝進socket的內核發送buffer中。后續我會專門用一篇文章介紹read和send所關聯的內核動作。每個UDP socket都有一個接收緩沖區,沒有發送緩沖區,從概念上來說就是只要有數據就發,不管對方是否可以正確接收,所以不緩沖,不需要發送緩沖區。
接收緩沖區被TCP和UDP用來緩存網絡上來的數據,一直保存到應用進程讀走為止。對于TCP,如果應用進程一直沒有讀取,buffer滿了之后,發生的動作是:通知對端TCP協議中的窗口關閉。這個便是滑動窗口的實現。保證TCP套接口接收緩沖區不會溢出,從而保證了TCP是可靠傳輸。因為對方不允許發出超過所通告窗口大小的數據。 這就是TCP的流量控制,如果對方無視窗口大小而發出了超過窗口大小的數據,則接收方TCP將丟棄它。 UDP:當套接口接收緩沖區滿時,新來的數據報無法進入接收緩沖區,此數據報就被丟棄。UDP是沒有流量控制的;快的發送者可以很容易地就淹沒慢的接收者,導致接收方的UDP丟棄數據報。
以上便是TCP可靠,UDP不可靠的實現。
TCP_CORK TCP_NODELAY
這兩個選項是互斥的,打開或者關閉TCP的nagle算法,下面用場景來解釋
典型的webserver向客戶端的應答,應用層代碼實現流程粗略來說,一般如下所示:
if(條件1){
向buffer_last_modified填充協議內容“Last-Modified: Sat, 04 May 2012 05:28:58 GMT”;
send(buffer_last_modified);
}
if(條件2){
向buffer_expires填充協議內容“Expires: Mon, 14 Aug 2023 05:17:29 GMT”;
send(buffer_expires);
}
。。。
if(條件N){
向buffer_N填充協議內容“。。。”;
send(buffer_N);
}
對于這樣的實現,當前的http應答在執行這段代碼時,假設有M(M<=N)個條件都滿足,那么會有連續的M個send調用,那是不是下層會依次向客戶端發出M個TCP包呢?答案是否定的,包的數目在應用層是無法控制的,并且應用層也是不需要控制的。
我用下列四個假設場景來解釋一下這個答案
由于TCP是流式的,對于TCP而言,每個TCP連接只有syn開始和fin結尾,中間發送的數據是沒有邊界的,多個連續的send所干的事情僅僅是:
假如socket的文件描述符被設置為阻塞方式,而且發送緩沖區還有足夠空間容納這個send所指示的應用層buffer的全部數據,那么把這些數據從應用層的buffer,拷貝到內核的發送緩沖區,然后返回。
假如socket的文件描述符被設置為阻塞方式,但是發送緩沖區沒有足夠空間容納這個send所指示的應用層buffer的全部數據,那么能拷貝多少就拷貝多少,然后進程掛起,等到TCP對端的接收緩沖區有空余空間時,通過滑動窗口協議(ACK包的又一個作用----打開窗口)通知TCP本端:“親,我已經做好準備,您現在可以繼續向我發送X個字節的數據了”,然后本端的內核喚醒進程,繼續向發送緩沖區拷貝剩余數據,并且內核向TCP對端發送TCP數據,如果send所指示的應用層buffer中的數據在本次仍然無法全部拷貝完,那么過程重復。。。直到所有數據全部拷貝完,返回。
請注意,對于send的行為,我用了“拷貝一次”,send和下層是否發送數據包,沒有任何關系。
假如socket的文件描述符被設置為非阻塞方式,而且發送緩沖區還有足夠空間容納這個send所指示的應用層buffer的全部數據,那么把這些數據從應用層的buffer,拷貝到內核的發送緩沖區,然后返回。
假如socket的文件描述符被設置為非阻塞方式,但是發送緩沖區沒有足夠空間容納這個send所指示的應用層buffer的全部數據,那么能拷貝多少就拷貝多少,然后返回拷貝的字節數。多涉及一點,返回之后有兩種處理方式:
1.死循環,一直調用send,持續測試,一直到結束(基本上不會這么搞)。
2.非阻塞搭配epoll或者select,用這兩種東西來測試socket是否達到可發送的活躍狀態,然后調用send(高性能服務器必需的處理方式)。
綜上,以及請參考本文前述的SO_RCVBUF和SO_SNDBUF,你會發現,在實際場景中,你能發出多少TCP包以及每個包承載多少數據,除了受到自身服務器配置和環境帶寬影響,對端的接收狀態也能影響你的發送狀況。
至于為什么說“應用層也是不需要控制發送行為的”,這個說法的原因是:
軟件系統分層處理、分模塊處理各種軟件行為,目的就是為了各司其職,分工。應用層只關心業務實現,控制業務。數據傳輸由專門的層面去處理,這樣應用層開發的規模和復雜程度會大為降低,開發和維護成本也會相應降低。
再回到發送的話題上來:)之前說應用層無法精確控制和完全控制發送行為,那是不是就是不控制了?非也!雖然無法控制,但也要盡量控制!
如何盡量控制?現在引入本節主題----TCP_CORK和TCP_NODELAY。
cork:塞子,塞住
nodelay:不要延遲
TCP_CORK:盡量向發送緩沖區中攢數據,攢到多了再發送,這樣網絡的有效負載會升高。簡單粗暴地解釋一下這個有效負載的問題。假如每個包中只有一個字節的數據,為了發送這一個字節的數據,再給這一個字節外面包裝一層厚厚的TCP包頭,那網絡上跑的幾乎全是包頭了,有效的數據只占其中很小的部分,很多訪問量大的服務器,帶寬可以很輕松的被這么耗盡。那么,為了讓有效負載升高,我們可以通過這個選項指示TCP層,在發送的時候盡量多攢一些數據,把他們填充到一個TCP包中再發送出去。這個和提升發送效率是相互矛盾的,空間和時間總是一堆冤家!!
TCP_NODELAY:盡量不要等待,只要發送緩沖區中有數據,并且發送窗口是打開的,就盡量把數據發送到網絡上去。
很明顯,兩個選項是互斥的。實際場景中該怎么選擇這兩個選項呢?再次舉例說明
webserver,,下載服務器(ftp的發送文件服務器),需要帶寬量比較大的服務器,用TCP_CORK。
涉及到交互的服務器,比如ftp的接收命令的服務器,必須使用TCP_NODELAY。默認是TCP_CORK。設想一下,用戶每次敲幾個字節的命令,而下層在攢這些數據,想等到數據量多了再發送,這樣用戶會等到發瘋。這個糟糕的場景有個專門的詞匯來形容-----粘(nian拼音二聲)包。

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

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

相關文章

leetcode144 二叉樹的前序遍歷

給定一個二叉樹&#xff0c;返回它的 前序 遍歷。 示例: 輸入: [1,null,2,3] 1 \ 2 / 3 輸出: [1,2,3] 進階: 遞歸算法很簡單&#xff0c;你可以通過迭代算法完成嗎&#xff1f; 思路&#xff1a;模仿遞歸的思路壓棧即可。 /*** Definition for a bi…

AJAX大總結

1、AJAX概述 1.1 什么是AJAX AJAX&#xff08;Asynchronous Javascript And XML&#xff09;翻譯成中文就是“異步Javascript和XML”。即使用Javascript語言與服務器進行異步交互&#xff0c;傳輸的數據為XML&#xff08;當然&#xff0c;傳輸的數據不只是XML&#xff09;。 …

我對STL的一些看法(一)初步認識STL

后面一段時間我將會給大家分享我自己學到STL以及應用的時候遇到的問題還有他的一些精髓,可能開始的邏輯會有些亂吧,不過后面還會不斷的整理和優化,讓自己看明白也讓更多的讀者看的清楚。 最近剛閑下來,先說說什么是STL: 不知道你是否有過這樣的經歷。在大學,你準備著手完…

PaperNotes(12)-Autoregressive Quantile networks for generative modeling

Autoregressive Quantile networks for generative modeling3 autoregressive implicit quantiles3 autoregressive implicit quantiles autoregressive&#xff1a;自身做回歸變量&#xff0c;用之前若干時刻的隨機變量 來建模 之后某些時刻 隨機變量的模型。 N維隨機變量的…

我對STL的一些看法(二)認識vector容器

先說vector吧。 C++ Vector(向量容器) 是一個線性順序結構。相當于數組,但其大小可以不預先指定,并且自動擴展。它可以像數組一樣被操作,由于它的特性我們完全可以將vector 看作動態數組。 vector 的數據安排以及操作方式,與 array 非常像似。兩者的唯一差別在于空間的…

git大總結

git init 在本地新建一個repo,進入一個項目目錄,執行git init,會初始化一個repo,并在當前文件夾下創建一個.git文件夾. git clone 獲取一個url對應的遠程Git repo, 創建一個local copy. 一般的格式是git clone [url]. clone下來的repo會以url最后一個斜線后面的名稱命名,創…

我對STL的一些看法(三)認識list容器

C++ List(雙向鏈表) 是一個線性鏈表結構,它的數據由若干個節點構成,每一個節點都包括一個信息塊(即實際存儲的數據)、一個前驅指針和一個后驅指針。它無需分配指定的內存大小且可以任意伸縮,這是因為它存儲在非連續的內存空間中,并且由指針將有序的元素鏈接起來。由于…

C++(4)--初識變量、數據類型

C變量1.C 命名規則2.C 命名規范3.C 數據類型sizeof ()4.聲明和使用變量4.1使用整型變量4.2使用單精度浮點型變量4.3使用雙精度浮點型變量5.附送-cout 設置寬度&#xff0c;對齊方式6.算術運算符和表達式6.1除法、取余6.2自加、自減7.強制類型轉換《老九學堂C課程》《C primer》…

我對STL的一些看法(四)認識deque容器

Deque(雙向隊列) 是一種優化了的、對序列兩端元素進行添加和刪除操作的基本序列容器。它允許較為快速地隨機訪問,但它不像vector 把所有的對象保存在一塊連續的內存塊,而是采用多個連續的存儲塊,并且在一個映射結構中保存對這些塊及其順序的跟蹤。向deque 兩端添加或刪除元…

我對STL的一些看法(五)初識關聯容器

3關聯容器 pair類型 這個是一個簡單的標準庫類型,該類型在utility頭文件中定義,我們來看看他主要的操作: pair<T1 ,T2> p1; 創建一個空的pair對象 pair<T1,T2> p1(v1,v2);創建一個pair對象,他的兩個元素分別為T1類型的v1,T2類型的v2 make_pair(v1,v2…

關系數據庫——mysql數據類型大總結

整數類型&#xff1a; 實數類型&#xff1a; 定點數&#xff1a;DECIMAL和NUMERIC類型在MySQL中視為相同的類型。它們用于保存必須為確切精度的值。 DECIMAL(M,D)&#xff0c;其中M表示十進制數字總的個數&#xff0c;D表示小數點后面數字的位數。 如果存儲時&#xff0c;整…

學點數學(5)--線性規劃對偶形式的理解

線性規劃對偶問題的理解1.弱對偶2.強對偶曾在上課的時候多次遇到這個求一個問題的對偶形式&#xff0c;大多是硬套公式。記一次&#xff0c;忘一次。后來在蘇大佬的博客中看到了相關闡述&#xff0c;感覺豁然開朗&#xff0c;&#xff08;可以記得就一些了&#xff09;遂做筆記…

關系數據庫——視圖/存儲過程/觸發器

視圖 視圖是虛擬的表&#xff0c;與包含數據的表不同&#xff0c;視圖只包含使用時動態檢索數據的查詢,主要是用于查詢。 為什么使用視圖 重用sql語句簡化復雜的sql操作&#xff0c;在編寫查詢后&#xff0c;可以方便地重用它而不必知道他的基本查詢細節。使用表的組成部分而…

C++(5)--運算符、表達式、條件結構(if, switch)

C運算符、表達式 條件結構1.表達式與運算符1.1賦值運算符1.2算術運算符1.3關系運算符1.4邏輯運算符1.5位運算符1.6 sizeof()1.7 三目運算符1.8 運算符的優先級2.條件結構2.1--if2.2 --switch結構2.3 switch 和 多重if 結構的對比條件結構)《老九學堂C課程》《C primer》學習筆記…

關系數據庫——mysql常用函數總結

文本處理函數 Left(x,len) – 返回串左邊的字符&#xff08;長度為len&#xff09; Right(x,len) Length(x) – 返回串的長度 Locate(x,sub_x) – 找出串的一個子串 SubString(x, from, to) – 返回字串的字符 Lower(x) Upper(x) LTrim(x) RTrim(x) Soundex(x) – 讀…

setsockopt()用法(參數詳細說明)

先來看看函數的原型: int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); 然后我們來看看參數: s(套接字): 指向一個打開的套接口描述字 level:(級別): 指定選項代碼的類型。

再議libcurl編程

那是2年前用libcurl了,我肯定好久不用的知識,放置久了就會遺忘,現在我又重拾起這個知識點,重頭再來,至于前面的基礎知識,可以參考我的 http://blog.csdn.net/pbymw8iwm/article/details/6675754 假設你要獲取URL所表示的遠程主機上的資源。你需要寫一段程序用來完…

關系數據庫——并發控制

并發控制 多用戶數據庫&#xff1a;允許多個用戶同時使用的數據庫&#xff08;訂票系統&#xff09; 不同的多事務執行方式&#xff1a; 1.串行執行&#xff1a;每個時刻只有一個事務運行&#xff0c;其他事務必須等到這個事務結束后方能運行。 2.交叉并發方式&#xff1a; …

C++(6)--初識循環while,do-while

初識循環1.使用while 循環結構2.使用do-while 循環3.python中的while循環《老九學堂C課程》《C primer》學習筆記。《老九學堂C課程》詳情請到B站搜索《老九零基礎學編程C入門》-------------簡單的事情重復做&#xff0c;重復的事情用心做&#xff0c;用心的事情堅持做(老九君…

map類的erase方法的在Linux與Windows中的差異

這次的代碼是跨平臺的,尼瑪在win32上通過,但是在linux上不通過了,查找了一下原來是平臺linux不支持。 有人舉了例子: std::map< int , float > i_f_map; i_f_map[1] = 1.2f; i_f_map[23] = 1.4f;