Linux之網絡

Linux之網絡

  • 兩個模型
  • 應用層協議
    • HTTPS
  • 傳輸層協議
    • UDP
    • TCP
      • 可靠性與效率的兼顧
      • 面向字節流
      • TCP異常情況
    • 底層實現
  • 網絡層協議
    • IP
    • 網段劃分
      • 子網劃分
      • NAT
  • 數據鏈路層協議
    • 以太網
    • ARP
  • 代理服務器
  • 內網穿透
  • 五種IO
    • 多路復用
    • Reactor模式

本文旨在講解tcp-ip協議原理,并不涉及代碼部分,具體應用代碼請參見我的git倉庫,本文默認你已經熟悉網絡通信代碼編寫流程。

兩個模型

iso提出的osi7層模型是完美的通信標準,后來所說的tcp/ip模型把上三層合為一個,但暗中遵循前者的三層劃分與解耦。
兩個模型
其中,osi的上三層,會話層負責管理會話;表示層:負責序列化和反序列化數據;應用層提供真正的服務,即處理請求。

應用層協議

HTTPS

傳輸層協議

UDP

udp,用戶數據報協議,無連接,不可靠,面向數據報。
無連接和不可靠等講到tcp就可以感受到了。udp報文

由圖可知,udp報文采用定長8字節報頭區分報頭與有效載荷,其中16位UDP長度標識該報文總長度,使得udp報文之間有邊界,同時應用層交給udp的數據,udp不做拆分與合并之類的控制而直接打報發送,以報文為基本單位便是udp面向數據報的由來。
當用戶層定義的結構體進入傳輸層前應當需要序列化成為字節流,但雙方os都是基于c語言,所以udp報頭不需要,但數據需要。
另外,可見udp報文最大長度64KB,有限,需要應用層考慮拆分長報文。
udp不需要發送緩沖區,通常直接交給os付與下層;有接收緩沖區,滿了丟棄。
sk_buff

上圖是截自內核源碼的sk_buff結構體的定義片段,它用于指向一塊由內核分配的、用于存放實際網絡數據的內存區域,即數據報所在空間。其中,head和end指向分配的整塊內存的起始和結束地址,data和tail指向當前協議層的數據報首尾。而next與prev用于管理sk_buff本身。
下面闡述一個應用層數據向下傳輸流程:當應用層調用send時,數據被復制到內核空間,并創建一個sk_buff結構來管理之,層層向下交付,根據該層所選協議在數據報添加相應報頭,即data前移出一個報頭位置,并寫入報頭信息,層層如此。
反之,接收到數據報時,創建sk_buff描述并管理之,到每一層都是去掉該層報頭,即取走報頭信息,data后移,再向上交付。

TCP

tcp,傳輸控制協議,面向連接,可靠傳輸,面向字節流。
TCP報文
tcp報文通過4位首部長度(單位4字節)分離報頭與有效載荷,報頭范圍20~60字節。序號,標識當前發送的數據段,通常填寫數據段起始字節編號(對tcp發送緩沖區數據按字節進行編號)。初始序列號由系統通過算法生成,避免舊報文干擾和序列號重用攻擊。
確認序號,表示該確認序號之前所有報文數據均已收到,并期望接下來的數據從確認號指定的位置開始。
六個標志位,URG配合16位緊急指針表示中數據中包含了一個需要優先處理、不排隊直接發送的緊急數據塊,后者指向其位置;ACK置1時表示確認序號字段有效;PSH提示接收方立即將緩沖區中的數據傳遞給應用層,而不是等待更多數據到達后再一次性交付;RST用于異常關閉連接,比如遇到錯誤的tcp段或端口未被監聽;SYN同步序列編號,配合序列號建立連接,見于三次握手;FIN終止連接,見于四次揮手。
窗口大小,報文發送者的tcp接收緩沖區當前剩余大小。
檢驗和,檢測報文段是否在傳輸過程中被損壞,確保可靠性,此處略過。
選項,可為空,其中有窗口擴大因子,表示實際窗口為窗口值左移因子位。

可靠性與效率的兼顧

  1. 序列號與確認應答:首先,每個字節的數據都被賦予一個序列號,報頭32位序號會填充為發送數據段中第一個字節的序列號,確保對方按序重組數據。同時,接收方在接收數據(假設其序號100,數據長度100)后會返回一個確認應答報文(其ACK標志位置1,以下簡稱該報文為ACK),該應答報頭中確認序號為接收報文的序號+數據長度+1,即201,表示該確認序號即201之前的數據均已接收,若發送方未在一定時間內收到ACK,則會重傳數據。
  2. 超時重傳:發送方發送一個數據包后,會啟動一個定時器。如果在這個定時器到期之前沒有收到相應的ACK,則認為該數據包丟失或損壞,并進行重傳。超時時間通常根據網絡條件動態調整,是最短的、應答一定能到達的時間。Linux采取指數退避的重傳機制,比如第一次超時500ms就重傳,后面超時2500、4500到8*500ms再重傳,累計多次超時再異常關閉連接。
    tcp連接管理
  3. 連接管理之三次握手:client通過connect發起三次握手,請求建立連接。雙方os自動完成如下圖的三次握手過程,在內核創建結構體描述并管理該連接,server調用accept將從已完成連接隊列中提取出已經成功完成了三次握手的新連接。三次握手的前兩次不能攜帶數據,因為握手尚未完成,第三次c->s可以捎帶應答,即發送數據時捎帶著確認應答。同時,三次握手會同步雙方初始序列號,交換雙方窗口大小。
ClientServerSYN(seq=x)SYN+ACK(seq=y, ack=x+1)ACK(seq=x+1, ack=y+1)ClientServer

問:為什么是三次握手?
答:三次握手以最短的流程驗證雙方本身通信意愿和網絡支持全雙工能力,即能收能發。
不能是兩次握手,因為如果少了第三次c發給s的ack,無法驗證c的全雙工能力。另外,如果上一次連接發送給s的舊SYN報文阻塞在網絡路由中,而連接斷開后該報文送達,那么在兩次握手的情況下,s只需要發送syn+ack即可建立連接,而c毫不知情,造成服務器連接隊列資源浪費。
其實,三次握手本質上是四次握手,只不過s的syn和ack合并發送,稱為捎帶應答,這是因為面對客戶端連接請求,服務器默認要無腦接受。

  1. 連接管理之四次揮手:一方比如client想要關閉連接,通過close關閉自己發送數據的方向而接收方向保留,os向對方發送帶有FIN的報文,server應答。s在發送完剩余數據后,再發送FIN報文,c給予應答則四次揮手完成。但是此時主動發起四次揮手的一方此處為c,會進入TIME_WAIT狀態,即使進程退出,在tcp層也會等待 2MSL(Maximum Segment Lifetime,最大報文生存時間)才能回到CLOSED狀態,而此期間該端口仍處于被占用狀態,無法被復用即綁定。另外,上面流程圖中可見如果服務端不調用close,四次揮手便無法完成,而會進入CLOSE_WAIT狀態,浪費fd與連接隊列資源。
ClientServerFIN(seq=u)ACK(ack=u+1)FIN(seq=v, ack=u+1)ACK(ack=v+1)TIME_WAIT (2MSL)CLOSEDClientServer

問:為什么主動斷開連接一方要處于TIME_WAIT狀態?
答:1.防止舊數據包干擾新連接,通過等待2MSL確保歷史上延遲或重復的數據報消失。2.確保最后一個ACK能被s收到。如果最后一個ACK丟失,s會重傳FIN,c在TIME_WAIT狀態下仍能收到FIN并重發ACK,避免s因收不到ACK而無法關閉連接。
問:如何在TIME_WAIT期間能夠復用該端口?
答:使用系統調用int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);其中,level填SOL_SOCKET表示套接字層,optname填SO_REUSEADDR啟用端口復用,optval傳int帶出選項值,optlen即int長度。

  1. 流量控制與滑動窗口:滑動窗口左邊界是上一次接收方發來的確認報文中確認序號處,滑動窗口大小size暫時認為上一次接收方方ACK中窗口大小,右邊界則由start+size確定。如此,發送方即可在不造成接收方緩沖區溢出(滿了會丟棄新到的報文)的前提下高效傳輸數據,實現流量控制。
    滑動窗口
    再來看滑動窗口,start是始終向右滑動的,對方發來一個ACK報文,即可根據確認序號更新start,如果1001 ~ 1500丟包,那么發出的兩個報文,只會受到兩個確認序號為1001的ACK,start不動。若1501 ~ 2000丟包,收到兩個確認序號1501的,start指向1501即可。
    另外,滑動窗口其實是環形的,循環利用,此處以線性方式看待較為方便。
    快重傳:當發送方收到三個重復ACK即確認序號相同,就會重傳確認序號對應報文,而不用等定時器超時。想象一下,滑動窗口里第一個報文丟失,后面收到3個相同ACK你就會重傳第一個報,但是反觀超時重傳,要等RTO(Retransmission Timeout)幾百毫秒級別的時間才能重傳,而丟包到快重傳可能只用幾十毫秒內。在這幾百ms里,發送方滑動窗口、應用層數據卡住,連接的吞吐量下降。
    當接收方接收緩沖區滿了時:接收方ACK窗口大小為0,發送方停止發送新數據,而是進行窗口探測,發送一個只包含1字節數據的小包(通常是對之前發送的數據進行重傳),多次探測等待應答;接收方如果有可用空間則會發送窗口更新通知。如果發送方探測達最大次數則終止連接。
  2. 擁塞控制:通過擁塞窗口+慢啟動與擁塞避免應對網絡擁塞問題。cwnd(congestion window,擁塞窗口)是當前網絡狀況能允許批量發送的數據量,從此,滑動窗口大小=min(窗口大小,cwnd)。連接建立或超時重傳后,發送方擁塞窗口會從1開始隨傳輸輪次指數增長,指數增長具有前期增速慢,后期增速爆炸的特點,所以稱為慢啟動,快速而安全地試探出網絡通暢情況。指數增長到ssthresh(慢啟動閾值)后改為線性增長,稱為擁塞避免。如果再次觸發超時重傳,ssthresh變為此時cwnd/2,稱為乘法減小,再重復慢啟動+擁塞避免。
    通過每個主機tcp層采取擁塞控制策略,網絡堵塞時狀況會大大改善。
  3. 延遲應答:接收方通過在不觸發超時重傳的前提下盡可能晚的發出應答,爭取時間使應用層讀走tcp緩沖區數據,使得ACK中窗口最大化,增大網絡吞吐量。

總結,tcp提高可靠性的機制有檢驗和,序列號與確認應答,超時重傳,三次握手,四次揮手,流量控制,擁塞控制;提高效率的策略有滑動窗口,快重傳,捎帶應答,延遲應答。
注意,可靠性是中性詞,tcp解決丟包、亂序等問題,但也相比udp損失了部分效率。二者應用場景不同,但無優劣之分。

面向字節流

個人理解:tcp層看待應用層交付的數據,并不關心整體性,它可以把應用層給我的一個包拆成100個tcp報文發出,由對方tcp再交給上層。它也可以將應用層給的100個包合并成一個tcp報文發出交予對方應用層。由此產生的諸如粘包問題,是指應用層的包黏在一起,需要應用層自己制定協議,劃分邊界。注意,tcp報文在tcp層就可以分離出報文數據,交付上層,tcp層不存在粘包問題。

TCP異常情況

進程終止,os會釋放fd,發送FIN,連接正常關閉。
機器重啟,與上同。
機器斷電/斷網,此時在線的一方如果發送數據會觸發超時重傳,就算不發送,也會有TCP自帶的保活機制,定期發送“心跳”數據包(即所謂的Keepalive探針,本質是空的ACK報文)或應用層自己實現的保活機制比如ping,來檢查對端是否仍然在線。一定時間內無應答則關閉連接。

底層實現

下圖為Linux網絡棧核心數據結構關系。方框中頭部為結構體名稱,+表示該結構體中成員

contains
contains fd_array[]
private_data points to
back reference
contains sk
sk_receive_queue
sk_write_queue
contains inet_conn
contains
contains (port mapping)
contains (connected/pending queues)
contains (pending queues)
contains
contains
task_struct
+files_struct* files
files_struct
+struct file* fd_array[]
file
+void* private_data
socket
+struct file* file_ref
+struct sock* sk
sock
+sk_buff_head sk_receive_queue
+sk_buff_head sk_write_queue
sk_buff_head
+multiple sk_buff linked together
tcp_sock
+inet_connection_sock inet_conn
inet_connection_sock
+inet_sock
+inet_bind_bucket* bind_bucket
+timer
+request_sock_queue req_queue
inet_sock
+src_ip, src_port, dst_ip, dst_port
+sock
+src_ip
+src_port
+dst_ip
+dst_port
inet_bind_bucket
request_sock_queue
+struct listen_sock *listen_opt;
+struct request_sock *rskq_accept_head;(connected queues)
+struct request_sock *rskq_accept_tail;(connected queues)
listen_opt
+struct request_sock *syn_table[0];(pending queues)
udp_sock
+inet_sock

其中,struct sock中含有字段struct {struct sk_buff *head; struct sk_buff *tail;} sk_backlog;與listen接口第二個參數有關,是內核維護的已完成三次握手但尚未被accept取走的連接請求隊列相當于候客區的最大長度。

網絡層協議

IP

ip,此處指ipv4,工作于網絡層,起路由功能。
IP報頭
版本,IP協議的版本號,在IPv4中該值為4。
首部長度和tcp一致,4字節為單位,最小值填5,加上選項會更長。
服務類型,指定數據包傳輸的服務優先級等。
總長度,ip數據報報頭+數據總長度,單位字節。
標識+標志+片偏移,共同完成分片工作。標識用于重組時標識屬于同一原始數據包的所有分片,標志第0位保留、第1位“不分段”標志、第2位“更多分片”標志,片偏移為該分片相對于原始數據包起始位置的偏移量,單位為8字節。

問1:接收方如何判斷報文是否被分片?
答1:如果標志第3位置1,則分片;如果“更多分片”為0但片偏移非0,則分片。
問2:接收方怎么保證把片收全?
答2:將所有標識相同的片聚合起來,“更多分片”標志置1且片偏移為0,則第一片收到,根據該片的數據長度片偏移=下一片的片偏移即可找至倒數第二片,最后一片“更多分片”為0。
問3:為什么要分片?
答3:如果ip報文長度超過MTU(Maximum Transmission Unit,最大傳輸單元,在以太網中為1500字節),會被分片,否則不能在數據鏈路層傳輸。另外,分片會增加丟失風險,一旦某個分片丟失或損壞,整個原始數據包就需要重傳。所以不推薦分片,為此也有了MSS(Maximum Segment Size,最大段大小),是tcp三次握手時協商取的雙方MTU最小值-20-20,以太網中為1460,減的是tcp和ip報頭,即一個tcp報文攜帶數據不應超過1460以避免分片。這個MSS也是滑動窗口的基本單位。

生存時間(TTL, Time to Live),限制數據報可以在網絡中經過的路由器數量,每經過一個路由器該值減1,當TTL變為0時數據報將被丟棄。
協議,IP數據報文內封裝的上層協議類型,如TCP為6,UDP為17。
頭部校驗和,保證IP報頭的完整性。

網段劃分

1981年,網絡發展早期,32位的ip地址被粗糙地劃分為如下幾類地址。A類網絡號少,適用于大型網絡,可容約1600萬主機,B類每個網絡可容約65000主機,而C類每個只容254臺主機(256-1個網絡地址-1個廣播地址)。如此固定的劃分方法,勢必造成網絡資源分配不均,比如只有幾百臺聯網設備的小公司被分配了A類地址,它無法完全利用1600萬個地址,造成浪費。
ABC類地址

子網劃分

1985年,引入子網掩碼進行子網劃分,可以在原有的網絡地址基礎上進一步靈活精細地劃分為多個子網,提供更細粒度的劃分方法。
先說明子網掩碼,比如,一個C類地址192.168.1.1(二進制形式1100 0000.1010 1000.0000 0001.0000 0001),對應子網掩碼為255.255.255.0(1111 1111.1111 1111.1111 1111.0000 0000),前24個1表示ip地址前24位為網絡號,后8個0表示地址后8位為主機號。將子網掩碼與該地址按位與可得到該IP地址所在的網絡地址即192.168.1.0,該ip地址可以表示為192.168.1.1/24,即ip地址/掩碼長度,表示前24位為網絡號,后8為主機號。注意,一個ip地址的主機號部分全0,如192.168.1.0/24,該ip代表該局域網;主機號全1,該ip為廣播地址。
下面介紹一個應用場景,展示子網劃分的意義:
假設一家公司有三個部門,分別需要50、100和200個IP地址。申請一個B類地址172.16.0.0/16(支持約65000臺主機),通過子網劃分為172.16.0.0/26(支持62主機)、172.16.0.128/25(支持126主機)和172.16.1.0/24(支持254主機)分配給三個部門使用,如此只使用了該3個子網的ip,模塊化分配子網,便于網絡靈活解耦管理。剩下6萬多ip被閑置,但仍屬于此公司可供未來使用,除非歸還否則其他組織無法使用,所以它并沒有解決IP不足,但為接下來的NAT技術鋪路。

NAT

盡管子網劃分優化了ip分配,但32位的ip地址上限是43億個,終究有用完的那天,一個有前途的解決方案是IPv6,用64位數字標識ip地址。但是,NAT的提出延長了IPv4的壽命,延緩了IPv6的全面部署。
NAT(Network Address Translation,網絡地址轉換),不同內網(即局域網)中復用私有ip,通過地址轉換來共享公網ip以訪問公網。用于組建內網的私有ip被規定為10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,它們不能出現在公網上,所以可以重復被不同內網使用,而公網ip在全球范圍內有唯一性,可以訪問公網。而大量設備在內網中被路由器動態分配(DHCP)有內網ip用于局域網通信,想獲取公網資源則將報文交與路由器,使用公網ip獲取資源,再由路由器返回,由此緩解公網ip不足的狀況,順帶提升安全性。


前置知識,路由器如何路由?
首先,路由器工作在網絡層(現代路由器在應用層),連接不同網絡,轉發網絡之間的IP數據包,所以路由器至少有兩套ip,一個LAN口ip即私有ip,一個WAN口ip即在更大的內網或公網中的ip。
路由表形如下圖,Destination目標網絡或主機地址,0.0.0.0或default表示默認路由;Gateway下一跳路由器的IP地址,0.0.0.0表示不經網關,接口直連;Genmask子網掩碼;Iface指定網絡接口發出。
route
路由器路由流程:路由器從某個接口(如以太網口、WAN口)接收到一個IP數據包,然后在路由表中查找與其目標ip匹配的路由條目,即一條一條地將Destination與Genmask做&,與目標ip對比,如果在同一個網絡,則通過Iface發往下一跳的路由器Gateway,沒找到目標網絡則走默認路由,沒默認路由則丟棄,并可能發送ICMP“目標不可達”消息回源主機,通知其數據包無法送達。ICMP自行了解。
內網中主機發送數據包之前,如果通過子網掩碼發現目標在同一局域網中,則走ARP協議,下面講;不在,說明目標在外網或不存在,則發給默認網關(通常是路由器)。


內網設備訪問公網流程:一個家庭主機進程(192.168.1.10:1234)試圖訪問www.baidu.com(180.101.49.12),發現不在本地網絡,則發給默認網關即路由器A(LAN口ip192.168.1.1,WAN口ip10.1.2.3),A查路由表,如果是首次訪問百度,則執行NAT,修改數據包源ip為自己的WAN口ip,記錄NAT表(建立ip+port的雙向映射,192.168.1.10:1234<–>10.1.2.3:5000,也可采用四元組映射),發給下一跳路由器B(LAN口ip10.1.2.1,WAN口ip203.0.113.1),B查表,執行NAT,修改數據包源ip為B的WAN口ip,記錄NAT表(10.1.2.3:5000<–>203.0.113.1:60000),此時終于可以進入互聯網,經過骨干網路由,到達百度服務器。返程則通過查NAT表映射并修改目的ip,數據包到B處,目的ip與port會被修改為10.1.2.3與5000,A處同理,最終響應報文交至家庭主機。
當多個設備都想通過NAT經由路由器A向公網訪問,而A只有一個WAN口ip,只建立ip映射并不唯一對應,所以NAT還會改變傳輸層的端口號,稱為NAPT(Network Address Port Translation)
所以,當許多家庭和小型辦公室網絡使用私有IP地址并共享少量公網ip時,ip不足就大為緩解了。

數據鏈路層協議

前面講的路由,解決的是子網與子網之間的數據包轉發,接下來的工作于數據鏈路層的以太網技術解決的是局域網內部數據包收發問題。
數據鏈路層有個子層叫MAC(媒體訪問控制)層,以太網幀就是在此層封裝而成,以以太網為例,下文MAC幀即以太網幀,MAC地址即硬件地址,相當于公交車標識每一站的地址,而ip地址是標識起點和終點的地址。

以太網

以太網技術是最常用的局域網通信技術,用于子網內通信。
以太網幀
6+6+2=14字節幀頭,4字節CRC檢驗幀尾,定長可分離;類型標識上層協議,ip報文填0800,arp請求/應答填0806,rarp請求/應答填8035,可分用。幀數據不多于MTU,不少于46字節,少了就補充到46字節。局域網是個碰撞域,同時發出去的兩個數據包都會損壞。所以,數據幀過大,發生碰撞的概率也大,過小,則發送的幀數過多,碰撞概率也大,加之對效率的考量,得出的幀數據長度區間。
但是,最開始,局域網內主機A只知道主機B的ip地址,并不知道主機B的MAC地址,那么發送數據包時,怎么填充MAC幀頭部目的MAC地址?于是有了ARP協議

ARP

ARP(Address Resolution Protocol,地址解析協議),通過主機廣播發送ARP請求幀來得知主機B的MAC地址。
ARP
上圖為ARP請求/應答,主機A發送前還需封裝以太網幀頭幀尾,由此可見ARP工作于MAC層上方,網絡層之下。
A會在硬件類型填1,表示鏈路層網絡為以太網;協議類型填0x0800表示上層協議為IPv4;硬件地址長度對于以太網填6;協議地址長度對于IPv4填4;操作碼填1表示ARP請求;發送方MAC與IP填自己的;目的MAC不知道,填全0;目的IP填對方。而以太網幀頭目的MAC填全F,表示廣播地址。
發出ARP請求后,局域網內所有主機都收到該報文,發現目的IP不為自己則丟棄;B發現目的IP是自己,且是arp請求,就會發送arp響應(操作碼填2表示ARP響應)。
A收到響應,通過響應中發送方MAC得知B的MAC地址,可以開始后續通信。同時,A會緩存下該ip與mac的映射直至過期被更新或刪除。

以太網交換機,工作于數據鏈路層,用于局域網內數據轉發。其上配有n個端口,不同主機連至不同端口而被劃分為n組,劃分出n個碰撞域,不同端口之間數據傳輸不相干擾。它也會維護MAC表,當有幀到來時,建立源MAC與端口映射,后期根據表轉發幀只至目的MAC所在端口即可。

代理服務器

代理服務器用來代替本人做轉發請求和響應。
正向代理面向客戶端,由于對外顯示代理服務器的ip,使客戶端匿名訪問;代理服務器可以提供訪問控制和內容過濾,比如校園網可以限制訪問某些網站;它也可緩存常用資源;繞過地域限制,即科學上網。
反向代理為服務端提供服務,通過將大量請求合理分配給各個后端服務器,實現負載均衡;可以緩存靜態內容比如圖片視頻,減輕服務器壓力;隱藏后端服務器ip。

內網穿透

內網穿透,讓內網中設備能被外網訪問。主要介紹3種方法。

  1. 端口轉發:手動配置路由器端口轉發規則,將外網對于該路由器特定ip+port的請求轉發到內網某個設備的特定私有ip+port上。
  2. 反向代理:在具有公網IP的服務器上部署特定服務比如frp作為中繼站,然后配置規則以轉發請求給內網主機。原理是,內網主機B先和中繼站主動發起長連接(穿透NAT,NAT轉化表已緩存),外網的主機A發送給中繼站的特定端口的數據流會被frp根據配置文件通過該長連接轉發給B,而tcp全雙工支持雙向通信。適用于訪問公司或家庭內網
  3. 內網打洞:一種P2P(peer-to-peer,點對點)通信技術,讓不同內網中的設備直接建立連接。依然需要一臺中繼服務器,兩個不同內網中的主機A和B分別和中繼站建立連接,中繼站再告知雙方對方的出入口路由器特定ip+port,A和B就可以直接通過訪問對方出入口路由器ip+port實現直接通信。見于視頻通話避開中介服務器轉發,降延遲;P2P傳輸文件;直播,無需平臺服務器轉發數據流

五種IO

首先,IO=等待+拷貝。其分五種,

  1. 阻塞式IO,阻塞式等待直至IO條件就緒,再拷貝,如read、write系列。。
  2. 非阻塞式IO,設置fd為非阻塞模式后,輪詢式檢測IO條件,不就緒會出錯返回,如EAGAIN或EWOULDBLOCK
  3. 信號驅動式IO,利用信號如SIGIO通知進程IO條件就緒,可設置信號處理方法
  4. 多路轉接/多路復用,一次監聽多個文件描述符的狀態變化,返回時指示哪些IO就緒,相當于將多個等待過程合并,面對高并發場景頗為高效,如select、poll等函數
  5. 異步IO,IO全程由os于后臺負責,進程只需發起IO操作,而不參與等或拷貝

非阻塞和多路復用代碼詳見git倉庫

多路復用

下面敘述select和poll和epoll之間的實現與優缺。
首先,它們都是讓用戶把等待的操作交給內核,可以一次等待多個事件,這是多路復用的定義。但是,select使用FD_SETSIZE大小的位圖描述fd狀態,有上限,通常是1024;poll雖然改用數組描述fd,沒有上限,但是在大規模并發連接的情況下效率較低,因為前二者都需要線性O(N)遍歷所有fd檢查是否有IO就緒,再將fd位圖或數組全部拷貝。而epoll使用紅黑樹來管理所有被監視的文件描述符,增刪查O(logN),能高效找出就緒fd并放入就緒隊列,一個雙向鏈表,從而允許快速遍歷已就緒fd而非所有,只拷貝就緒fd。這是底層結構差異,epoll數據結構優化,拷貝開銷降低。
其次,select和poll本質是輪詢,需要定期遍歷所有fd。而epoll基于事件驅動,只有在IO就緒時才通知喚醒處在該等待隊列下的進程,減少cpu浪費。在高并發情況下,前二者性能下降,而epoll依然堅挺。這是底層機制的差異,epoll高效適用于高并發。
epoll工作流程:

  1. epoll_create在內核創建struct eventpoll,包含紅黑樹根節點struct rb_root rbr、fd就緒隊列struct list_head rdllist和進程等待隊列wait_queue_head_t,以及單鏈表串起struct epitem*。其返回的epfd可找到struct file,file里的private data指向eventpoll。
  2. epoll_ctl+ADD時,內核創建struct epitem,其通過內含struct rb_node rbn插入至紅黑樹中。epitem含有struct epoll_filefd ffd,其含struct file *file和int fd,所以它是fd的真正描述體,eventpoll是管理體。內核還會為該fd注冊一個回調函數,io就緒觸發硬件中斷時,會調用回調將其epitem添加至就緒隊列尾,并喚醒wait在該fd下的進程隊列。
  3. epoll_wait,內核檢查就緒隊列,不為空將其復制到用戶傳入的數組中,為空繼續阻塞等待。注意,就算設定了timeout,只要有fd就緒,wait就會立刻返回,除非timeout內無事件就緒才會超時返回。

epoll默認工作于LT(Level Trigger,水平觸發)模式,即只要fd是就緒的,epoll就會將其放入就緒隊列通知你;ET(Edge Trigger,邊沿觸發)模式,即只有fd狀態變化時比如不可讀變可讀、新數據到來,epoll才會通知你。ET時,假如用戶一次沒從緩沖區中讀完數據,則epoll不會再通知你去讀,所以ET必須配合while循環讀取緩沖區而且得是非阻塞,直到EAGAIN到來循環結束,如果是阻塞一旦讀完就會阻塞等待。而LT可以一次讀取部分數據,因為epoll會因為數據仍在、fd仍就緒而持續提醒上層。
ET一方面倒逼上層一次性取完數據,提高通知效率;一方面增大己方接收窗口和對方滑動窗口大小,提高傳輸效率。

Reactor模式

采用epoll+非阻塞IO實現事件驅動、多路復用以應對高并發場景,這個模式就稱為Reactor(反應堆)模式。
下面提供本人親測的三套reactor代碼,分別是單進程、多進程、多線程版本,思路均為master進程/線程負責管理/分發連接,worker進程/線程負責處理網絡IO。
其實,還有一套代碼,思路是多進程,父進程負載均衡通過管道通知+子進程繼承listensock并處理IO,但是debug中,就不放了。


到這,Linux部分全部結束,后面本人要開始搞項目準備實習了,bye bye

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

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

相關文章

MCP(模型上下文協議):是否是 AI 基礎設施中缺失的標準?

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

基于粒子群優化算法優化支持向量機的數據回歸預測 PSO-SVM

一、作品詳細簡介 1.1附件文件夾程序代碼截圖 全部完整源代碼&#xff0c;請在個人首頁置頂文章查看&#xff1a; 學行庫小秘_CSDN博客?編輯https://blog.csdn.net/weixin_47760707?spm1000.2115.3001.5343 1.2各文件夾說明 1.2.1 main.m主函數文件 該代碼實現了使用PSO…

版本更新!FairGuard-Mac加固工具已上線!

FairGuard-Mac加固工具1.0.2版本更新日志&#xff1a;■ 支持 AssetBundle 資源加密;■ 支持 Unity global-metadata 文件加密;AssetBundle &#xff0c;是 Unity 提供的一種資源存儲壓縮包。其中儲存了游戲的資源&#xff0c;如圖片、模型、紋理、音視頻、代碼等文件。AssetBu…

【Linux篇章】穿越數據迷霧:HTTPS構筑網絡安全的量子級護盾,重塑數字信任帝國!

本篇摘要 本篇文章將從https是什么&#xff0c;為什么需要https角度&#xff0c;基于之前學的http[速戳速通HTTP]認識https&#xff0c;介紹什么是加密等&#xff0c;認識加密的兩種方式&#xff1a;對稱加密和非對稱加密&#xff1b;引出五種不同的通信方加密方式外加滲透證書…

數據庫:表和索引結構

表和索引是如何組織和使用的&#xff0c;在很大程度上取決于具體的關系型DBMS&#xff0c;然而它們都依賴于大致相似的結構和原則。索引頁和表頁表行和索引行都被存儲在頁中。頁的大小一般為4kb&#xff0c;這是一個可以滿足大部分需求的大小&#xff0c;也可以是其他大小&…

Java 學習筆記(基礎篇5)

1. 綜合練習(1) 抽獎public class test10 {public static void main(String[] args) {int[] arr {2,588,888,1000,10000};Random r new Random();for (int i 0; i < arr.length; i) {int randomIndex r.nextInt(arr.length);int temp arr[randomIndex];arr[randomIndex…

P1162 填涂顏色(染色法)

P1162 填涂顏色 - 洛谷 #include <bits/stdc.h> using namespace std; #define ll long long const int N 1e7 10; int n; int a[100][100],b[110][110]; int dx[4]{-1,1,0,0}; int dy[4]{0,0,1,-1}; void dfs(int x,int y) {if(x<0 || x>n1 || y<0 || y>n…

Webrtc在項目中承擔的角色

一、簡單劃分 解決方案層:負責對SDK的對接、操作業務邏輯、UI封裝、采集、渲染等,屬于基礎業務邏輯層 會議SDK層:負責對會議業務邏輯的封裝、服務端交互、創會/加會/離會等,屬于會議業務邏輯層 mediasoupclient層: 負責對webrtc封裝,提供會議層面相關接口,屬于webrtc業務…

Servlet上傳文件

這是一個Maven項目tomcat版本&#xff1a;9.0.107pom.xml<project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.…

cocos creator 3.8 - 精品源碼 -《漢中漢:漢字中的字》

cocos creator 3.8 - 精品源碼 - 超級文字大師游戲介紹功能介紹免費體驗下載開發環境游戲截圖免費體驗游戲介紹 《漢中漢&#xff1a;漢字中的字》、找漢字&#xff0c;是一款從文字中的筆畫找出可以組成新漢字的小游戲。比如&#xff1a;“王”字中的筆畫就可以組成&#xff…

手機端的音視頻界面或者圖片文檔界面共享給大屏

手機端的音視頻界面或者圖片文檔界面共享給大屏&#xff0c;可通過無線投屏和有線連接等技術手段實現&#xff0c;以下是具體介紹&#xff1a;無線投屏&#xff1a;AirPlay&#xff1a;這是蘋果公司開發的無線共享協議。蘋果手機可通過上滑或下拉調出控制中心&#xff0c;點擊 …

Linux內存管理系統性總結

Linux內存管理系統性總結 內存管理核心架構圖 #mermaid-svg-hKRdgBBYXZTiost8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-hKRdgBBYXZTiost8 .error-icon{fill:#552222;}#mermaid-svg-hKRdgBBYXZTiost8 .error-t…

MySQL 運算符實戰:9 道經典練習題解析

MySQL 運算符實戰&#xff1a;9 道經典練習題解析 運算符是 MySQL 查詢的 “靈魂”&#xff0c;靈活運用各類運算符能讓數據篩選更加精準高效。本文通過 9 道實戰練習題&#xff0c;詳解邏輯運算符、比較運算符及模糊匹配的用法&#xff0c;幫你快速掌握運算符的核心應用場景。…

【R語言】更換電腦后,如何在新設備上快速下載原來設備的 R 包?

【R語言】更換電腦后&#xff0c;如何在新設備上快速下載原來設備的 R 包&#xff1f; 在日常使用 R 進行數據分析時&#xff0c;我們往往會安裝很多包&#xff08;packages&#xff09;&#xff0c;一旦更換電腦&#xff0c;手動一個一個重新安裝會非常麻煩。本文介紹一種簡單…

如何在 Ubuntu 24.04 或 22.04 LTS 上安裝 PowerShell

在本教程中,我們將學習如何在 Ubuntu 24.04 Noble 或 22.04 Jammy JellyFish Linux 中通過命令終端安裝 Microsoft Windows PowerShell。 Windows PowerShell 既是一個命令行外殼程序,也是一種腳本語言。它擁有超過 130 個遵循一致語法和命名約定的命令行工具,稱為 cmdlet(…

基于支持向量機的數據回歸預測(libsvm) SVM

一、作品詳細簡介 1.1附件文件夾程序代碼截圖 全部完整源代碼&#xff0c;請在個人首頁置頂文章查看&#xff1a; 學行庫小秘_CSDN博客?編輯https://blog.csdn.net/weixin_47760707?spm1000.2115.3001.5343 1.2各文件夾說明 1.2.1 main.m主函數文件 這段 MATLAB 代碼實現…

Flowith-節點式GPT-4 驅動的AI生產力工具

本文轉載自&#xff1a;Flowith-節點式GPT-4 驅動的AI生產力工具 - Hello123工具導航 ** 一、節點式 AI 工作流革新者&#xff1a;Flowith 深度解析 二、產品核心定位 Flowith 是一款基于 GPT-4 Turbo 的節點式 AI 生產力工具&#xff0c;突破傳統單線程聊天模式&#xff0c…

MySQL的事務日志:

目錄 redo&#xff08;重做日志&#xff09;&#xff1a; 特點&#xff1a; 組成&#xff1a; 整體流程&#xff1a; redo log buffer與redo log file之間的刷盤策略&#xff1a; 異步刷盤&#xff1a; 同步刷盤&#xff1a; 拆中策略&#xff1a; undo&#xff08;回…

JavaScript 中 throw error 與 throw new Error(error) 的用法及區別,分別適合什么場景使用?

JavaScript 中 throw error 與 throw new Error(error) 的用法及區別 在 JavaScript 中&#xff0c;throw 關鍵字用于拋出異常。當代碼遇到某些錯誤或異常情況時&#xff0c;可以通過拋出錯誤來通知程序&#xff0c;方便后續的錯誤處理。盡管 throw 的使用看似簡單&#xff0c…

鴻蒙自帶組件效果大全

圖形變換-視效與模糊-通用屬性-ArkTS組件-ArkUI&#xff08;方舟UI框架&#xff09;-應用框架 - 華為HarmonyOS開發者 注意:找到需求效果之后先對一下版本 視距 圖像效果 圖片裁剪 顏色漸變 前景屬性設置 外描邊設置: 視效設置: 組件內容模糊 運動模糊 點擊回彈效果…