目錄
- 1.UDP
- (1)傳輸層
- (2)UDP報頭
- (3)緩沖區和sk_buff
- ①緩沖區
- ②sk_buff
- 2.TCP
- (1)發送和接受緩沖區
- (2)報頭結構
- ①按序到達
- ②可靠傳輸
- ③流量控制
- ④緊急指針
1.UDP
(1)傳輸層
UDP處在傳輸層(應用層之下,網絡層之上),負責將數據從發送端傳輸到接收端。 其中我們已經知道:IP標識主機,端口號標識主機內的進程。因此,源IP、目的IP、源端口號、目的端口號、使用的運輸層協議這五個元素(五元組)可以標識一個通信。
其中0 - 1023為知名端口號,和常見服務強綁定,如HTTP等。一個進程可以bind多個端口號,從多個端口接收數據,但一個端口號不能被多個進程bind,每個端口接收的數據只能上交給一個進程。
HTTP協議我們已經認識了,傳輸層協議本質也是一種約定,其底層實現是C語言。不僅如此,網絡層、數據鏈路層都有自己的協議,OS內核各個模塊的通信也有自己的協議。其中OS內核通信實現可以直接用二進制傳輸數據,無需做明顯的序列化,并且效率高、兼容性好。
(2)UDP報頭
和應用層一樣,添加報頭在邏輯上是在要傳輸的數據前后包裝一些約定的對象,在代碼上就是定義相應協議的結構體對象,調用功能函數把我們的數據和要添加的數據合并起來,這就是封裝。
解析報頭時,UDP直接讀取收到的報文前8個字節,剩下就是有效載荷。報文的前8個字節包括:16位源端口號和16位目的端口號,16位當前UDP報文有效載荷長度(自描述字段),16位校驗和。
由于記錄了16位當前UDP報文有效載荷長度和16位校驗和,因此報文會被完整合法地交給上層。如果出現了UDP檢驗和失敗,那這個報文會被直接丟棄,而無需向接收方響應任何數據。
在這里我們要特別說明一下UDP是不可靠傳輸,不可靠在哪?16位當前UDP報文有效載荷長度,16位校驗和都無法保證可靠性嗎?UDP的不可靠之處在于如果數據不完整的話會直接丟棄,上層卻不會知道這一切。而TCP就有自己的重傳機制,會保證接收方一定會收到相同字節數的數據。
現在我們再次強調UDP三個特性的含義,以免混淆:
無連接意味著無需listen和connect;不可靠意味著沒有確認和重傳機制等;面向數據報意味著接收方必須 像收快遞那樣一次性必須全部把一個數據包傳入應用層(不會出現粘包問題,只能一塊一塊地IO),而相對而言面向字節流就是以bit為單位的(這也是TCP需要序列化的根本原因)。
我們要消除一個誤解,就是可靠和不可靠其實并不是優缺點,而是特性。TCP要做很多處理才能保證可靠傳輸,不可靠傳輸往往意味著協議會更簡單。UDP在直播、視頻領域很常用,因為一些丟包根本不影響,而像支付交易等才更需要TCP。
(3)緩沖區和sk_buff
①緩沖區
UDP封裝在代碼上就是創建相應的結構體對象,在創建時會同時申請一塊緩沖區,就是UDP協議的緩沖區。UDP有接收緩沖區,但這個緩沖區接收的順序不一定和對方發送的順序一致,可能報文2比報文1先到,這也是不可靠的一種。相比而言TCP就沒有這個情況,它會有序號保證數據的順序性。當UDP接收緩沖區被寫滿后,再收到的數據會直接丟。
UDP沒有也不需要發送緩沖區,UDP在收到應用層的數據后,會接添加報頭直接往下傳,而不需要做處理,但這不影響其全雙工的特性。
②sk_buff
sk_buff是負責管理報文的結構體,它屬于整個網絡協議棧,即從鏈路層到應用層,始終是同一個sk_buff在管理這個報文。從面向對象的思想來看,我們可以認為對報文的管理就是對sk_buff的管理,這在后面理解緩沖區是sk_buff鏈表這件事上至關重要。
struct sk_buff
{// 協議層頭部指針__u16 transport_header; // 傳輸層頭部位置__u16 network_header; // 網絡層頭部位置__u16 mac_header; // 鏈路層頭部位置//其余部分省略
};
當一個報文的sk_buff被創建后會由指針管理數據。通過修改指針指向就可以填充UDP報頭字段,也就完成了UDP報頭封裝,其它協議也是這樣。
單個UDP報文包含首部能傳輸的最大數據約為64k字節(MTU限制)。因此要傳輸更多數據,就要拆分報文,多次發送,多個報文由sk_buff組成的鏈表管理。UDP的接收緩沖區就是報文的sk_buff鏈表組成的。
2.TCP
(1)發送和接受緩沖區
TCP也有自己的緩沖區,且同時有發送接受緩沖區。當要發送時,數據會直接拷貝進緩沖區;當讀取時,也會直接從接受緩沖區拷貝數據。而緩沖區就由OS自主決定什么時候發,什么時候收,和上層解耦。
我們前面已經提過,管理報文就是管理sk_buff,緩沖區是本應是char數組,但這樣并不好處理。所以考慮以面向對象的思想來處理,TCP的發送/接受緩沖區和UDP的接受緩沖區都是sk_buff鏈表,從緩沖區讀數據、寫數據本質就是對sk_buff進行增刪查改。
(2)報頭結構
TCP的報頭結構相對比較復雜,但就像在UDP說的,TCP之所以復雜是因為它需要實現一些特性,所以接下來我會根據TCP要實現的獨特性質分類講解報頭結構,具體性質后續會拓展。
①按序到達
前面說過UDP不保證接收的順序,而TCP需要。因此TCP需要給自己的每一個報文添加一個字段——32位序號。這樣接收方就知道收到的數據屬于哪個序號了。進一步,接收方也就知道現在還有哪些序號的數據沒有收到了,這也能進一步實現可靠傳輸了。
需要注意的是,TCP中要傳輸的報文的每一個字節都有自己的序號,多個字節組成一個TCP報文,報頭的序號就是起始報文對應的序號。
在這里又引入了一個問題:既然一個TCP報文有多個字節,而報頭只記錄第一個字節的序號,那么接收方怎么知道應該讀多少字節呢?報文邊界在哪呢?
首先我們要清楚,發送方從應用層接收數據,再創建對象封裝,是肯定知道邊界的,每個TCP數據段往下傳的時候網絡層也能分清,也就是說我們的疑惑來自于接收方。對于接收方來說,網絡層解包后就會把整個數據交給TCP,這個時候TCP難道還不知道邊界嗎?網絡層 -> 傳輸層交付的過程就已經有邊界信息的交付了。
TCP報頭中沒有記錄報文長度的字段,TCP可以借助其它層確認邊界
但是這樣還不夠,接收方能夠確認邊界了,報文的邊界呢?對于UDP來說,它的報頭是固定長度8字節,而TCP有選項部分,可以多添加一些選項,所以TCP的報文長度相對更靈活,那么報文的起始位置就需要單獨存儲了,這就是——4位數據偏移,且偏移以4字節為單位。最小值5對應報文數據從20開始,報頭占0 - 19共20字節;最大值15對應60字節的報頭(TCP允許最多40字節的選項)
②可靠傳輸
下面的數據就是多個TCP報文,報頭序號分別是1,1001,2001,3001,后續的例子都將建立在它之上。
有了序號之后就有機會實現可靠傳輸,具體過程是怎樣的?
當發送序號為1的報文時,1 - 1000序號(共1000字節)的數據被發送和接收,接收報文之后接收方會發出一個回應,回應包含一個數字1001,告訴發送方1001之前的數據都收到了,這時發送方再從1001開始發送第二個報文,再回應,以此類推。這個回應中包含的確認的數字就是——32位確認序號。 如果此時報文2丟了,回應的就還是1001,那么這個時候發送方就會重傳第2個報文。 但這樣效率并不高,因此發送方會一直發送報文,而接收方收到一個報文就回應,回應中就能體現出丟沒丟包,這樣發送和回應就并行了。
有了確認序號之后,就能實現捎帶應答機制了。從剛才的情況出發,發送方給接收方發報文,接收方通過確認序號回應,但如果回應時接收方發現自己想要給發送方再帶一些消息,怎么辦呢? 這個時候接收方就可以把要傳的數據放在報文中,同時填充序號。這樣發送方得到回應后,從序號處可以得到接收方想要給自己的數據,從確認序號那里也能得到確認信息,一舉兩得,這就是捎帶應答機制。
捎帶應答是一個可有可無的功能,比如接收方收到一個報文,它怎么知道這是一個單純的應答,還是說有捎帶應答?用序號和確認序號區分確實是一種方法,但是,TCP中這種可有可無的行為可不止捎帶應答,還有一些后續會提及,所以 TCP報頭需要集中管理,控制TCP連接的狀態和數據傳輸行為,即6位控制位。
我們可以看到,當ACK == 1時,表示確認序號有效,這個時候接收方才會去解讀確認序號。通過這張圖可看出,控制位在TCP中非常重要,標識不同字段的有效性,算是給TCP報頭進行一種分類了。
③流量控制
和UDP一樣,TCP發送和接收緩沖區都是有大小的。UDP接收緩沖區滿了就直接丟棄多的數據,TCP也是。但TCP要保證可靠傳輸,發送方發了多少數據要保證接收方能接收多少數據,這就意味著它需要盡可能避免這種情況,除了重傳機制以外還要有流量控制,這就需要——16位窗口大小(0 - 65535字節)。
接收方和發送方進行TCP通信時,每個報頭都要帶上自己的窗口大小,就是自己的接受緩沖區的剩余的大小。 前面我說過發送方會連續發送多個報文,接收方再對接收的每個報文做回應,但是發送方在一定時間內連續發送報文數量的上限是什么呢?這就要引入滑動窗口了。 滑動窗口就是建立在TCP發送緩沖區之上的,是TCP決定什么時候從緩沖區向外發數據的關鍵結構。 滑動窗口大小的決定因素之一就是對方回應的16位窗口大小,滑動窗口大小不會超過16位窗口大小對應的值,這樣就能保證發送方不會發送過多數據,以至于溢出接收方的接受緩沖區導致數據丟棄。
④緊急指針
TCP傳輸中,總會產生特殊情況需要緊急傳輸的數據,因此TCP還支持16位緊急指針(需要在控制位URG == 1時生效)。當緊急指針生效時,TCP按序到達就會被打破,OS優先讀取處理緊急數據(如使用百度云盤,上傳數據時用戶申請終止上傳,這個終止的指令就比較緊急,不需要等到前面大量數據被按序收到)。 緊急指針就是一個標記有效載荷中緊急數據的偏移量。緊急數據的長度呢?緊急數據只占1個字節,不能被大量使用。
其余部分,如端口、檢驗和等就不多贅述了,和UDP一致。