1. UDP協議
1.1 UDP協議格式
系統內的UDP協議結構體:
注1:UDP協議的報頭大小是確定的,為8字節
注2:可以通過報頭中,UDP長度將UDP協議的報頭和有效載荷分離,有效載荷將存儲到接收緩沖區中等待上層解析。
注3:UDP沒有真正意義上的發送緩沖區,調用sendto會直接交給內核,由內核將數據傳給網絡層協議后進行后續傳輸動作
注4:UDP具有接收緩沖區,該緩沖區不能保證收到UDP報文的順序和發送UDP報文的順序一致,如果緩沖區滿了,再到達的UDP數據會被丟棄。
認知:操作系統會接收各種協議的大量的報文,因此os需要對報文做管理,而每個報文都是一個結構體(類),該類保存了報頭和有效載荷等其他相關屬性信息,該結構體可以通過某些數據結構統一管理起來。
注1:一個struct sk_buff對應一塊內存空間記錄報文數據,這和先前學習 struct file_struct 也會指向對應file是類似的
注2:網絡通信,每一層都是報頭+有效載荷的結構。
head和end標志整個緩沖區的開始和結束,假設數據從應用層一直遞交到網絡層,開始時data指向應用層數據開始地址處,tail指向尾部,每一層加入新的報頭時,data往上添加
2. TCP協議
2.1 TCP協議格式
2.2?序號和確認序號
認知:TCP協議是面向字節流的協議,序號和確認序號是報文中有效載荷在整個數據流中的編號,如果將字節流抽象成一個一維數組,那么編號就是數組下標,即序號確定了報文有效載荷的開始與結束
注:SYN和FIN標志位會占用序號,其他標志位不會占用序號
2.3? 四位首部長度
認知:用于記錄TCP報頭長度,基本單位是4字節,TCP協議規定了報頭的長度范圍為20~60字節,因此四位報頭長度的數值應為[5,15]。
2.4? 窗口大小
現象:TCP協議,通信的雙方存在各自的接收緩沖區,實際通信過程中,如果多個客戶端向服務端發起通信請求,那么可能會導致服務端的接收緩沖區占滿。占滿之后,多余的客戶端請求會被丟棄,導致資源浪費和低效。
認知:為了解決上述問題,窗口大小的目的是為了流量控制,是為了告訴對方,我的接收緩沖區剩余空間大小,避免資源浪費和低效。當客戶端知道了服務端的窗口大小,會控制自己的發送速度。
2.5 6/8位標志位
問:為什么TCP報頭中要有標志位?
答:接收方會收到不同類型的TCP報文,針對不同的報文類型,接收方要有不同的做法,因此需要有標志報文不同類型的字段,所以就需要這個標志位。
注:實際是位段,屬于哪個類型就哪個位置一,否則置零
2.5.1 SYN標志位
作用:同步、建立連接,在握手過程中使用的標志位
2.5.2 ACK標志位
作用:表明是一個應答報文
注:大多數情況下,ACK標志位常置為1,只有第一次握手時為0,后續討論。
2.5.3 FIN標志位
作用:表明是一個關閉連接的報文
2.5.4 PSH標志位
作用:催促接收方盡快將接收緩沖區內的數據交給上層
2.5.5 RST標志位
作用:連接重置報文,雙方連接出現任何問題時,都可以進行重置
3. TCP協議中的三次握手☆☆☆
3.1 第一次握手
解釋:
??SYN = 1:
SYS標志位置1,SYN標志位是用來建立連接的,因此,當客戶端第一次向服務端發起握手請求時,報文中的SYN標志位會被置1
??Seq:
是客戶端OS隨機生成的一個序列號,用于建立連接
??ACK = 0:
第一次發送請求時,不會包含應答,所以ACK置0
細節1:雙方在握手過程中,實際發送的是報文!
細節2:第一次握手時,報文中只包含報頭數據,不包含有效載荷?
3.2 第二次握手
服務端在接收到客戶端發來的報文時,服務端也會將SYN置1,同時發送一個隨機序號y,同時將確認序號置為:Ack = x + 1,告知客戶端,下一次從x + 1序號位置發送數據。
細節:第二次握手時,報文中依然只有報頭,沒有有效載荷
3.3 第三次握手
客戶端接收到服務端發送來的應答,表明x+1之前的序號已經連接成功,第一次握手成功,此時客戶端向服務端發送應答 Ack = y + 1,告知服務端,下次從y+1開始的序號發送數據。
當服務端再次收到客戶端的應答時,雙方都建立了可靠的通信。
☆☆☆細節:ACK不占用序號,所以在雙方三次握手完畢,建立起連接后,客戶端的確認需要就為seq = x + 1;客戶端在后續發送帶有有效載荷的數據時,就從該序號開始,這樣雙方在第一次通信時,就能夠保證發送方的起始序號和接收方的確認序號能夠一致
3.4 細節問題
問:前面我們說了,序號是用來形容有效載荷的,而且第一次握手和第二次握手中,不包含有效載荷,那么第一次握手和第二次握手中的序號Seq是什么?
答:SYN和FIN標志位會被計入到序號中,只占1位
問:序號和確認序號究竟是什么?有什么作用?
答:
??序號:每個字節在數據流中的開始位置,告知對方這次發送的數據是從該序號開始的,每個字節都有唯一序號,在數據發送過程中不斷遞增。
??確認序號:告訴接收方期望收到的下一個字節的序號,并告知對方我已經成功收到了你從開始序號到該序號為止的數據
舉兩個例子:
1.三次握手(假設握手成功):
??客戶端向服務端發送序號Seq?= 100,有效載荷的大小和SYN標志位決定了序號的范圍,即1
??服務端接收客戶端發來的序號 Seq = 100,服務端OS解析TCP報文長度,得知序號范圍為100~101,所以Ack = Seq + 1 = 101,同時隨機設置一個序號Seq = 200,發送給客戶端。
??客戶端接收到服務端的應答,表明從100~101的序號服務端已經全部接收完畢,客戶端OS會解析服務端發送來的報文,得知序號范圍為200~201,所以應答 Ack = Seq + 1 = 201,發送給服務端。
2.握手成功后正常通信(假設標志位全為0,雙方有效載荷的大小為50字節):
??客戶端向服務端發送序號Seq = 100
??服務端接收客戶端發來的報文,告知客戶端下次從150序號開始發送數據,同時回復Ack = 150,并隨機發送自己的序列號 1000
??客戶端接收服務端的應答,客戶端的數據從150序號開始發送,同時告知服務端下次數據從1050序號開始發送。
問:為什么三次握手前兩次需要有SYN標志位?
答:SYN是用于建立連接的標志位,TCP通信是全雙工通信,需要確認雙方可以互通
問:為什么要三次握手?
答:1.驗證服務端和客戶端能進行全雙工通信的最短的方式,本質是驗證網絡暢通;2.以最小成本100%確認雙方通信意愿
問:假設第三次握手時,服務端沒有收到來自客戶端的回應?
答:那么握手就失敗了,客戶端覺得,只要把ACK發出,他就認為三次握手完成,但實際情況是:服務端沒有接收到客戶端的回應,就會導致出現一個建立是否成功認知不一致的問題,即客戶端認為連接建立成功,而服務器認為連接建立失敗。
此時若客戶端向服務端發送報文時,服務器就會識別到當前沒有成功建立連接,就會向客戶端發送一個帶RST標志位的報文,要求重新建立連接。
細節1:connect發起握手請求,三次握手的過程由 client OS 和 client OS 完成,accpet不參與三次握手的過程
細節2:丟包問題,當發送方收不到應答 && 接收Ack超時時
問:這個時間有多長?
答:根據網絡情況定長短,以500ms為基準,第二次傳以500*2為基準 ,第三次傳以500*4為基準,如果500*4成功了,下次就以500*4為基準
細節3:TCP在握手時,向對方傳遞的不僅僅是SYN、ACK標志位,而是一整個報文,保溫內部有對方的窗口大小信息,因此在握手過程中,雙方就確認了對方的窗口大小信息,以便控制自己滑動窗口的大小進行流量控制
4. TCP協議中的四次揮手☆☆☆
4.1 四次揮手的過程(假設客戶端主動斷開連接)
第一次揮手:
? ? ? 客戶端調用close(fd); 關閉套接字,發送帶有FIN標志位的報文,表示要關閉客戶端,客戶端進入FIN_WAIT_1狀態;
第二次揮手:
? ? ? ? 服務端接收客戶端發來的FIN報文,返回帶有ACK的報文,表示服務端知道客戶端要關閉了,此時服務端進入CLOSED_WAIT狀態
第三次揮手:
? ? ? ? 服務器處理完剩余數據后,主動發送帶有FIN標志位的報文,表示要關閉服務端,服務端進入LAST_ACK狀態
第四次揮手:
? ? ? ? 客戶端收到服務端的FIN后,發送ACK表示客戶端知道服務端關閉了,此時客戶端進入TIME_WAIT狀態,大約兩個MSL時間后,客戶端完全關閉進入CLOSED狀態
細節:上述是客戶端主動斷開連接的情況,如果是服務端主動斷開連接,對應狀態變化只需要互換即可。
4.2 細節問題
問:如果客戶端關閉/退出,服務端不關會出現什么情況?
答:服務端會一直處于close_wait狀態,依舊占用fd,連接沒有釋放 → 服務端文件描述符泄漏問題
注:客戶端關閉時,服務端也需要調用close(),關閉對應套接字,即發起第三次揮手過程。
問:MSL是什么?
答:MSL:TCP協議通信雙方互發的歷史報文中,存活時間最大的報文。
問:為什么要等待兩倍的MSL?在回答這個問題之前,需要對另一個現象做一下描述。
現象:當發送方發送一個數據時,發送方判斷超時,認為該數據已經丟失,但實際情況可能是該數據還在路由器的等待隊列當中,即還存在于網絡當中。此時如果斷開的服務器立馬重啟,那么在建立連接的過程中,網絡中的數據可能會被重新接收,從而影響建立連接的過程
答:為此發起斷開的那一方在最后一次發送ACK時,需要處于TIME_WAIT狀態同時等待兩倍的MSL時間,為的就是網絡中兩個方向上殘存的報文消散,避免引起下一次建立通信連接時出錯。
注1:這也就是為什么主動斷開的那一方為什么無法立刻重啟服務器,因為他處于TIME_WAIT的狀態,對應的端口號無法被使用,得換一個端口號才能重啟。歷史上被丟棄的報文就不會和新的連接配得上(源ip port 目的 ip port),雙方os就能夠甄別出來這是陳舊報文
注2:如果需要讓服務端又要處于TIME_WAIT狀態,又要立即重啟,需要重新設置套接字:
5. 滑動窗口
滑動窗口的作用:用于流量控制,是流量控制的具體實現方案
滑動窗口的大小:無需等待確認應答而可以繼續發送數據的最大值
滑動窗口是發送緩沖區的一部分:窗口左邊是已經發送完畢的數據,會被清空;窗口內部是待發送的數據;窗口右邊是尚未發送的數據。
問:滑動窗口的大小有誰決定?
答:滑動窗口的大小由對方接收能力決定,簡單點的話,滑動窗口的start = 報文確認序號
問:滑動窗口可以向左滑動嗎?
答:不可以
問:滑動窗口可以變小、變大、不變以及為零嗎?
答:可以
問:如果對端的窗口大小滿了呢?
答:滑動窗口的大小設置為0,
1. 發送端周期性的進行窗口探測(發送只帶報頭的報文)來獲取對端窗口大小屬性。
2. 對端窗口更新時也會向發送端發送窗口更新的報文(報頭)
問:如果滑動窗口內的數據丟失了怎么辦?
丟失分為三種:左側、中間以及右側丟失,后兩種丟失都可以變為左側丟失,因此只對左側丟失做分析
答:假設發送方發送的序號為1001~2000、2001~3000、3001~4000、4001~5000。
①.?如果現在發送方在發送時,1001~2000的數據丟失了,而2000后的數據接收方正常接收。
這里需要注意一點:那就是發送方發過來的起始序號必須和接收方的確認序號是完全相同的。接收方的確認序號為1001,那么發送方發過來數據的起始序號必須為1001,如果1001~2000的數據丟失了,后續所有接收方收到的報文的確認序號都為1001,發送方在接收接收方的應答時,就還是從1001序號開始發送報文,如果發送方收到了多個相同確認序號,就會觸發快重傳,否則就是慢重傳。
再來復習一下確認序號的定義:假設確認序號為x,告知對方,前x個序號已經被成功接收,下一次從x+1開始的序號發送數據。
上述情況,如果1001~2000的數據接收成功,那么接收方的確認序號會被重置為seq = 2000+1 = 2001,作為應答傳給發送方,告知前2001個序號接收成功,下一個數據從2001序號開始,但是如果接收方接收失敗,后續收到的報文的起始序號和當前接收方的確認序號不一致,那么接收方會以1001作為確認序號來應答。此時滑動窗口的大小不會更新!
②. 如果1001~2000數據的應答丟失,發送方收到了3001的確認序號,說明3001序號之前的數據已經接收完畢,此時可以更新滑動窗口的大小了,更新到3001序號處。
6. 擁塞控制
6.1 相關概念
擁塞窗口(cwnd):決定了發送端發送緩沖區滑動窗口的大小
滑動窗口大小 = min(擁塞窗口,對端接收緩沖區剩余空間)
注:網絡情況是實時變化的,這決定了擁塞窗口也是實時更正的,擁塞窗口大,客戶端的滑動窗口可能越大,能夠發送的數據越多。
ssthresh:慢啟動閾值
網絡擁塞:在TCP網絡通信過程中,出現大面積的丟包問題。
6.2 擁塞控制策略
具體方式:
1.?慢啟動階段
? ? ? ? 擁塞窗口指數增長,開始增長慢,探測網絡情況,后續增長快,盡快占用帶寬
2.?擁塞避免階段
? ? ? ? 當擁塞窗口超過慢增長閾值時,開始線性增長,避免過快增長導致出現網絡擁塞
3. 小面積丟包觸發快/慢重傳
? ? ? ? 當發生丟包時,接收方會發送重復的ACK,發送方可以快速重傳丟失的數據包,
? ? ? ? 數據恢復后,擁塞窗口較小至原來的一半,并通過線性增長逐步恢復
4.?大面積丟包重新進入慢啟動階段
? ? ? ? 如果發生網絡擁塞,慢開始閾值變為擁塞窗口的一半,擁塞窗口從1開始重新進入慢開始階段
注:當發生網絡擁塞時,此時客戶端不能立即重傳數據,因為成千上億的用戶如果同時重傳,會導致網絡負擔進一步加大
7. 延時應答
概念:接收端在接收到對方報文,可以先等一下再答復,等待期間上層可能已經報這條報文處理完了,或者先前的報文處理完了,這樣窗口的大小可能不變或者變大,再應答給發送端,這樣發送端的窗口有概率會增大,發送更多數據
注:這個等待時間不會超過發送方的等待時間