傳輸層
在計算機網絡中,傳輸層是將數據向上向下傳輸的一個重要的層面,其中傳輸層中有兩個協議,TCP,UDP 這兩個協議。
TCP
話不多說,我們直接來看協議報頭。
源/目的端口號:表示數據從哪個進程來,到哪個進程去;
序號(Sequence Number)
在數據的傳輸中,傳輸報文的數據部分的每一個字節,都有一個他自己的編號。序號(Sequence Number),簡稱SN。
SN與SYN標志控制位的值有關,SYN值不同,SN表達不同的含義:
當SYN為1時,說明此時為連接建立階段,這時的SN為初始序號:ISN(Intial Sequence Number),通過隨機生成SN。
當SYN為0時,說明現在是數據傳輸階段,第一個報文的序號是ISN+1,后面的報文的序號, 當前的報文的SN值+Tcp報文的凈荷字節數(不包括Tcp報頭) 。eg.如果發送端發送的報文的SN為3,他的凈荷字節數為20,那么發送端發送的下一個報文的SN為20 。
在實際的數據傳輸中,SN的作用是當我們主機收到很多報文時,我們可以利用SN值對當前報文進行一個去重效果。
確認序號(Acknowledge Number)
對當前收到的序號進行一個確認。如果設置了一個ACK控制位,確認序號表示一個準備接受的包的序列號,注意,他的序列號指向的是準備接受的包,也就是下一個期望接受的包的序列號。
舉個例子,假設發送端(如Cient)發送3個凈荷為1000byte、起始SN序號為1的數據包給Server四服務端,Server每收到一個包之后需要回復一個ACK響應確認數據包給Client。ACK響應數據包的ACKNumber值,為每個Client包的為SN+包凈荷,既表示Server已經確認收到的字節數,還表示期望接收到的下一個Cient發送包的SN序號。(三次握手詳細圖解)。
數據偏移(首部長度)
4位Tcp報頭長度:表示該TCP 頭部有多少個32 位bit(有多少個4 字節); 所以TCP 頭部最大長度是15 * 4 = 60
標志控制位
· URG:占一位,表示緊急指針字段有效。在實際中,優先處理緊急字段,此時緊急字段指針才有用,并且指向緊急數據(應用較少,一般用來錯誤碼,eg.在傳輸中,突然不要某個數據了)。
· ACK:置位ACK=1表示確認號字段有效:TCP協議規定,連接建立后所有發送的報文的ACK必須為1;當ACK=0時,表示該數據段不包含確認信息。當ACK=1時,表示該報文段包括一個對已被成功接收報文段的確認序號Acknowedgment Number,該序號同時也是下一個報文的預期序號。
· PSH:表示當前報文需要請求推(push)操作;當PSH=1時,接收方在收到數據后立即將數據交給上層,而不是直到整個緩沖中區滿。(在窗口檢測中,發送PSH,將消息進行交互)。
· RST:置位RST=1表示復位TCP連接;用于重置已經混亂的連接,也可用于拒絕一個無效的數據段或者拒絕一個連接請求。如果數據段被RST置了RST位,說明報文發送方有問題發生。
· SYN:在連接建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連接清求報文,對方若同意建立連接,則應在響應報文中使SYN=1和ACK=1。綜合下,SYN置1,就表示這是一個連接請求或連接接受報文。
· FIN:用于在釋放TCP連接時,標識發送方比特流結束,用來釋放一個連接。當 FIN=1時,表明此報文的發送方的數據已經發送完畢,并要求釋放連接。
窗口大小
通俗來講,就是表示自己的接收緩沖區的剩余空間大小。用來進行流量控制。
校驗和
對整個TCP報文段,即TCP頭部和TCP數據進行校驗和計算,接收端用于對收到的數據包進行驗證。
緊急指針
它是一個偏移量,和SN序號值相加表示緊急數據最后一個字節的序號。以上內容是TCP報文首部必須的字段,也稱固有字段,長度為20個字節。接下來是TCP報文的可選項和填充部分。
可選項和填充部分
可選項和填充部分的長度為4n字節(n是整數),該部分是根據需要而增加的選項。如果不足4n字節,要加填充位,使得選項長度為32(4字節)的整數倍,具體的做法是在這個字段中加入額外的零,以確保TCP頭是32位(4字節)的整數倍。
Tcp三次握手
三次握手是Tcp面向連接的重要過程,和確保了數據傳輸的可靠性。
(1)第一次握手:CIient進入SYN SENT狀態,發送一個SYN幀來主動打開傳輸通道,該幀的SYN標志位被設置為1,同時會帶上Client分配好的SN序列號,該SN是根據時間產生的一個隨機值,通常情況下每間隔4ms會加1。除此之外,SYN幀還會帶一個MSS(最大報文段長度)可選項的值,表示客戶端發送出去的最大數據塊的長度。
(2)第二次握手:Server接受SYN幀之后,會進入SYN RCVD,同時返回SYN+ACK幀給Client,主要目的在于通知Client,Server端已經收到SYN消息,現在需要進行確認。Server端發出的SYN+ACK幀的ACK標志位被設置為1,其確認序號AN(Acknowledgment Number)值被設置為Client的SN+1,SYN+ACK幀的SYN標志位被設置為1,SN值為Server端生成的SN序號,SYN+ACK幀的MSS(最大報文長度)
(3)第三次握手:Client在收到Server的第二次握手的SYN+ACK確認幀之后,首先將自己的狀態會從SYN SENT變成ESTABLISHED,表示自己方向的連接通道已經建立成功,Client可以發送數據給Server端了。然后,Client發AGK幀給Server端,該ACK幀的ACK標志位被設置為1,其確認序號AN(Acknowledqment Number)值被設置為Server端的SN序號+1,還有一種情況,Client可能會將ACK幀和一幀要發送的數據,合并到一起發送給Server端。
(4)Server端在收到Client的ACK幀之后,會從SKN RCVD狀態會進入ESTABLISHED狀態,至此,Server方向的通道連接建立成功,Server可以發送數據給Client,TCP的全雙工連接建立完成。
如下圖:
四次揮手
(1)第一次揮手:主動斷開方(可以是客戶端,也可以是服務器端),向對方發送一個FIN結束請求報文,此報文的FIN位被設置為1并且正確設置Sequence Number(序列號)和Acknowledgment Number(確認號)。發送完成后,主動斷開方進入FIN_WAIT_1狀態,這表示主動斷開方沒有業務數據要發送給對方,準備關閉SOCKET連接了。
(2)第二次揮手:正常情況下,在收到了主動斷開方發送的FIN斷開請求報文后,被動斷開方會發送一個ACK響應報文,報文的Acknowledqment Number(確認號)值為斷開請求報文的SN+1,該ACK確認報文的含義是:“我同意你的連接斷開請求”。之后,被動斷開方就進入了(CLOSE_WAIT)狀態,TCP協議服務會通知高層的應用進程,對方向本地方向的連接已經關閉,對方已經沒有數據要發送了,若本地還要發送數據給對方,對方依然會接受。被動斷開方的==CLOSE_WAIT(關閉等待)==還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。主動斷開方在收到了ACK報文后,FIN _WAIT_1轉換成FIN_WAIT_2狀態。
(3)第三次揮手:在發送完成ACK報文后,被動斷開方還可以繼續完成業務數據的發送,待剩余數據發送,或者CLOSE-WAIT(關閉等待)截止后,被動斷開方會向主動斷開方發送一個FIN+ACK結束響應報文,表示被動斷開方的數據都發送完了,然后,被動斷開方進入LAST_ACK狀態。
(4)第四次揮手:主動斷開方收在到FIN+ACK斷開響應報文還需要進行最后的確認,向被動斷開方發送一個ACK確認報文然后,自己就進入TIME_WAIT狀態,等待超時后最終關閉連接。處于TIME_WAIT狀態的主動斷開方,在等待完成2MSL的時間后,如果期間沒有收到其他報文,則證明對方已正常關閉,主動斷開方的連接最終關閉。
被動斷開方在收到主動斷開方的最后的ACK報文以后,最終關閉了連接,自己啥也不管了。
小問
為什么建立連接是三次握手?為什么不是一次兩次?
因為三次握手,C/S雙方都有一次的確定的收發。并且中間兩次(SYN和ACK被捎帶應答了)。它也能確定通信雙方是健康的。
四次揮手可以是三次揮手嗎?
可以,當C發送close請求時,剛好S也發送了close請求,這種情況就和三次握手一樣,同樣被做捎帶應答了,但是這種情況就很少。
洪水攻擊
SYN洪水攻擊(SYN Flood Attack) 是一種常見的 DDoS攻擊(分布式拒絕服務攻擊),利用 TCP協議的三次握手缺陷 耗盡服務器資源,使其無法正常響應合法用戶的請求。防御需結合協議優化(如SYN Cookie)和流量過濾技術。
Tcp特性
滑動窗口
如果我們每次都是一發一接受,他的效率就會低很多,性能就會低一點。
如下圖:
如果我們同時發很多數據呢,這樣不就解決了效率低下的問題(其實是將多個段的等待時間重疊在一起了)。
? 窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值。上圖的窗口大小就是4000 個字節(四個段)。
? 發送前四個段的時候, 不需要等待任何ACK, 直接發送;
? 收到第一個ACK 后, 滑動窗口向后移動, 繼續發送第五個段的數據; 依次類推;
? 操作系統內核為了維護這個滑動窗口, 需要開辟發送緩沖區來記錄當前還有哪些數據沒有應答; 只有確認應答過的數據, 才能從緩沖區刪掉;
? 窗口越大, 則網絡的吞吐率就越高;
如下圖:
快重傳
在上面的傳輸過程中,在同時傳輸多個數據報文時,如果遇到某個數據包丟包了,這種情況下Tcp會怎么做呢?
Tcp首先會讓確實的數據包進行一個重傳。其余的同時傳輸的數據包也會收到,但是不會進行一個重發。
如下圖:
流量控制
接收端處理數據的速度是有限的. 如果發送端發的太快, 導致接收端的緩沖區被打滿, 這個時候如果發送端繼續發送, 就會造成丟包, 繼而引起丟包重傳等等一系列連鎖反應.因此TCP 支持根據接收端的處理能力, 來決定發送端的發送速度. 這個機制就叫做流量控制(Flow Control);
接收端將自己可以接收的緩沖區大小放入TCP 首部中的"窗口大小" 字段, 通過ACK 端通知發送端;表示自己能接受的數量大小。
窗口大小字段越大, 說明網絡的吞吐量越高;
接收端一旦發現自己的緩沖區快滿了, 就會將窗口大小設置成一個更小的值通知給發送端;
發送端接受到這個窗口之后, 就會減慢自己的發送速度;
如果接收端緩沖區滿了, 就會將窗口置為0; 這時發送方不再發送數據, 但是需要定期發送一個窗口探測數據段, 使接收端把窗口大小告訴發送端。
發送窗口探測包被接受后,發送端會發送一個Ack其中它的一個標志位PSH也將置1,表示我需要盡快收到數據。
擁塞控制
因為網絡上有很多的計算機, 可能當前的網絡狀態就已經比較擁堵. 在不清楚當前網絡狀態下, 貿然發送大量的數據, 是很有可能引起雪上加霜的,少量的丟包, 我們僅僅是觸發超時重傳; 大量的丟包, 我們就認為網絡擁塞。
擁塞控制, 歸根結底是TCP 協議想盡可能快的把數據傳輸給對方, 但是又要避免給網絡造成太大壓力的折中方案。
TCP 引入慢啟動機制, 先發少量的數據, 探探路, 摸清當前的網絡擁堵狀態, 再決定按照多大的速度傳輸數據;這樣也確保了一個可靠性。
擁塞控制的窗口=min(擁塞窗口大小,接收端反饋的窗口大小)。
因為他作為一個高效的傳輸,需要一個快速的擁塞窗口增長速度。
因為接受的能力有限,因此不能單純使擁塞窗口變大。就需要一個閾值,來限制一下他的增長速度。讓他的增長的速度減緩。
在每次超時重發的時候, 慢啟動閾值會變成原來的一半, 同時擁塞窗口置回1;少量的丟包, 我們僅僅是觸發超時重傳; 大量的丟包, 我們就認為網絡擁塞;當TCP 通信開始后, 網絡吞吐量會逐漸上升; 隨著網絡發生擁堵, 吞吐量會立刻下降;
如下圖
延遲應答
如果接收數據的主機立刻返回ACK 應答, 這時候返回的窗口可能比較小。
假設接收端緩沖區為1M. 一次收到了500K 的數據; 如果立刻應答, 返回的窗口就是500K;
但實際上可能處理端處理的速度很快, 10ms 之內就把500K 數據從緩沖區消費掉了;
在這種情況下, 接收端處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也能處理過來;
如果接收端稍微等一會再應答, 比如等待200ms 再應答, 那么這個時候返回的窗口大小就是1M;
一定要記得, 窗口越大, 網絡吞吐量就越大, 傳輸效率就越高. 我們的目標是在保證網絡不擁塞的情況下盡量提高傳輸效率;
如下圖:
捎帶應答
在延遲應答的基礎上, 我們發現, 很多情況下, 客戶端服務器在應用層也是"一發一收"的。意味著客戶端給服務器說了"How are you", 服務器也會給客戶端回一個"Fine,thank you";那么這個時候ACK 就可以搭順風車, 和服務器回應的"Fine, thank you" 一起回給客戶端。
我們的三次握手,也是采用的是捎帶應答,接收端收到數據后,我們會將一個ACK+SYN一起發送給發送端。
面向字節流
創建一個TCP 的socket, 同時在內核中創建一個發送緩沖區和一個接收緩沖區;
? 調用write 時, 數據會先寫入發送緩沖區中;
? 如果發送的字節數太長, 會被拆分成多個TCP 的數據包發出;
? 如果發送的字節數太短, 就會先在緩沖區里等待, 等到緩沖區長度差不多了, 或者其他合適的時機發送出去;
? 接收數據的時候, 數據也是從網卡驅動程序到達內核的接收緩沖區;
? 然后應用程序可以調用read 從接收緩沖區拿數據;
如下圖:
eg.
由于緩沖區的存在, TCP 程序的讀和寫不需要一一匹配, 例如:
? 寫100 個字節數據時, 可以調用一次write 寫100 個字節, 也可以調用100 次write, 每次寫一個字節;
? 讀100 個字節數據時, 也完全不需要考慮寫的時候是怎么寫的, 既可以一次read 100 個字節, 也可以一次read 一個字節, 重復100 次;
粘包問題
因為他是一個全雙工的,并且面向字節流,就會導致它的一個發送的數據不全或者是發送的數據多了一點。
那么如何避免粘包問題呢? 歸根結底就是一句話,明確兩個包之間的邊界。
對于定長的包, 保證每次都按固定大小讀取即可; 例如上面的Request 結構, 是固定大小的, 那么就從緩沖區從頭開始按sizeof(Request)依次讀取即可;對于變長的包, 可以在包頭的位置, 約定一個包總長度的字段, 從而就知道了包的結束位置;對于變長的包, 還可以在包和包之間使用明確的分隔符(應用層協議, 是我們自己來定的, 只要保證分隔符不和正文沖突即可);
對UDP來說不會存在粘包問題,因為他每次發送的就是一個數據報格式的。UDP發送的數據要么收到,要么不收到。
小結
TCP 復雜,是因為要保證可靠性, 同時又盡可能的提高性能。
可靠性:
校驗和
序列號(按序到達)
確認應答
超時重發
連接管理
流量控制
擁塞控制
提高性能:
滑動窗口
快速重傳
延遲應答
捎帶應答
這就是TCP的主要內容。