TCP 原理 -三次握手四次揮手-
原文: https://blog.csdn.net/qq_50156012/article/details/123391854
一、TCP 協議
TCP,即 Transmission Control Protocol,傳輸控制協議。人如其名,要對數據的傳輸進行一個詳細的 控制。
TCP 協議段格式
源/目的端口號:
表示數據是從哪個進程來,到哪個進程去;
序列號:
在建立連接時由計算機生成的隨機數作爲其初始值,通過 SYN 包傳給接收端主機,每發送一 次數據,就「累加」一次該「數據字節數」的大小。用來解決網絡包亂序問題。
確認應答號:
指下一次「期望」收到的數據的序列號,發送端收到這個確認應答以後可以認爲在這個序號以前的數據都已經被正常接收。用來解決不丟包的問題。
6 位標誌位:
-
URG:
緊急指針是否有效 -
ACK:
確認號是否有效,該位爲 1 時,「確認應答」的字段變爲有效,TCP 規定除了最初建立連接時的 SYN 包之外該位必須設置爲 1 。 -
PSH:
提示接收端應用程序立刻從 TCP 緩衝區把數據讀走 -
RST:
對方要求重新建立連接;我們把攜帶 RST 標識的稱爲復位報文段,該位爲 1 時,表示 TCP 連接中出現異常必須強制斷開連接。 -
SYN:
請求建立連接;我們把攜帶 SYN 標識的稱爲同步報文段,該位爲 1 時,表示希望建立連接,並在其「序列號」的字段進行序列號初始值的設定。 -
FIN:
通知對方,本端要關閉了,我們稱攜帶 FIN 標識的爲結束報文段,該位爲 1 時,表示今後不會再有數據發送,希望斷開連接。當通信結束希望斷開連接時, 通信雙方的主機之間就可以相互交換 FIN 位爲 1 的 TCP 段。
二、TCP 原理
TCP 對數據傳輸提供的管控機制,主要體現在兩個方面:安全和效率。這些機制和多線程的設計原則類似:保證數據傳輸安全的前提下,儘可能的提高傳輸效率。
1,確認應答機制
2,超時重傳機制
因此主機 B 會收到很多重複數據。那麼 TCP 協議需要能夠識別出那些包是重複的包,並且把重複的丟棄掉。這時候我們可以利用前面提到的序列號,就可以很容易做到去重的效果。
這時候我們可以利用前面提到的序列號,就可以很容易做到去重的效果。
最理想的情況下,找到一個最小的時間,保證 "確認應答一定能在這個時間內返回"。
但是這個時間的長短,隨着網絡環境的不同,是有差異的。
如果超時時間設的太長,會影響整體的重傳效率;
如果超時時間設的太短,有可能會頻繁發送重複的包;
TCP 爲了保證無論在任何環境下都能比較高性能的通信,因此會動態計算這個最大超時時間。
Linux 中(BSD Unix 和 Windows 也是如此),超時以 500ms 爲一個單位進行控制,每次判定 超時重發的超時時間都是 500ms 的整數倍。
如果重發一次之後,仍然得不到應答,等待 2*500ms 後再進行重傳。
如果仍然得不到應答,等待 4*500ms 進行重傳。依次類推,以指數形式遞增。
累計到一定的重傳次數,TCP 認爲網絡或者對端主機出現異常,強制關閉連接。
3,連接管理機制
SYN 同步報文段,嘗試和對方建立連接,JavaSocket API 中,客戶端 new Socket 內核就會發起這樣的 SYN 請求
SYN 這個標誌位爲 1,表示是一個同步報文段
我能聽見(ACK),你能聽見我嗎(SYN)
建立連接的過程,相當於通信雙方各自給對方發送 SYN,再各自給對方發送 ACK 中間的 ACK 和 SYN 和二爲一,於是最後就是三次握手
能否只有兩次握手
服務端狀態轉化:
[CLOSED -> LISTEN] 服務器端調用 listen 後進入 LISTEN 狀態,等待客戶端連接;
[LISTEN -> SYN_RCVD] 一旦監聽到連接請求(同步報文段),就將該連接放入內核等待隊 列中,並向客戶端發送 SYN 確認報文。
[SYN_RCVD -> ESTABLISHED] 服務端一旦收到客戶端的確認報文,就進入 ESTABLISHED 狀 態,可以進行讀寫數據了。
[ESTABLISHED -> CLOSE_WAIT] 當客戶端主動關閉連接(調用 close),服務器會收到結束 報文段,服務器返回確認報文段並進入 CLOSE_WAIT;
[CLOSE_WAIT -> LAST_ACK] 進入 CLOSE_WAIT 後說明服務器準備關閉連接(需要處理完之 前的數據);當服務器真正調用 close 關閉連接時,會向客戶端發送 FIN,此時服務器進入 LAST_ACK 狀態,等待最後一個 ACK 到來(這個 ACK 是客戶端確認收到了 FIN)
[LAST_ACK -> CLOSED] 服務器收到了對 FIN 的 ACK,徹底關閉連接。
客戶端狀態轉化
[CLOSED -> SYN_SENT] 客戶端調用 connect,發送同步報文段;
[SYN_SENT -> ESTABLISHED] connect 調用成功,則進入 ESTABLISHED 狀態,開始讀寫數據;
[ESTABLISHED -> FIN_WAIT_1] 客戶端主動調用 close 時,向服務器發送結束報文段,同時 進入 FIN_WAIT_1;
[FIN_WAIT_1 -> FIN_WAIT_2] 客戶端收到服務器對結束報文段的確認,則進入 FIN_WAIT_2,開始等待服務器的結束報文段;
[FIN_WAIT_2 -> TIME_WAIT] 客戶端收到服務器發來的結束報文段,進入 TIME_WAIT,併發 出 LAST_ACK;
[TIME_WAIT -> CLOSED] 客戶端要等待一個 2MSL(Max Segment Life,報文最大生存時 間)的時間,纔會進入 CLOSED 狀態。
重要的轉狀態
-
1:LISTEN:服務器啓動完畢,隨時可以有客戶端來連接
-
2:ESTABLISHED:建立連接成功,隨時傳輸消息
服務器調用 new ServerSocket 就會綁定端口號,並且進入 LISTEN 狀態
客戶端調用 new Socket,就會嘗試和服務器建立連接並觸發三次握手
三次握手不能只握兩次,如果沒有最後一個 ACK,此時主機 B 是無法知道自己發送能力和對方接受能力是否正常
三次握手,握手四次可以但沒必要,中間的 SYN 和 ACK 是同一時刻觸發的
-
3:CLOSE_WAIT:四次揮手揮手一半剩下的兩次就不揮手了(接收方沒調用 close 方法,就會導致四次揮手只揮手兩次,從而沒有正確關閉連接)。
-
4:TIME_WAIT:誰主動斷開連接,誰進入 TIME_WAIT 狀態,此時主機已經完成四次揮手過程,但是仍然不能立即釋放,要等 TIME_WAIT 狀態保持一定時間之後釋放
三次握手和四次揮手過程出現丟包就會觸發超時重傳
4,滑動窗口
沒有滑動窗口的機制下,傳輸 N 份數據,就需要等待 N 次應答時間,總的傳輸時間:N 份數據傳輸時間 + N 份應答時間。
滑動窗口的實質就是批量傳輸數據,總的傳輸時間:N 份數據傳輸時間重疊成 1 份時間。
窗口:不等待 ACK 的情況下,批量發送的最大數據量,就叫窗口大小
滑動:形象的比喻,窗口的範圍就是表示當前哪些數據在等待 ACK,隨着一個 ACK 到達,就立刻發送下一個數據,等待的數據包範圍就在逐漸滑動
窗口的大小不變,當發送方收到 2001 的 ACK,就意味着 1001-2000 的數據對方已經收到,此時立刻傳輸 5001-6001 的數據,此時等待的 ACK 數據包序號就是 2001、3001、4001、5001.
ACK 丟了
1001 這個 ACK 丟了,2001 這個 ACK 沒丟,就認爲 1-1000 這個數據也是順利到達的,1001 丟了就丟了,無所謂,2001 能夠包含 1001ACK 中的信息。
數據包就直接丟了
如果數據報丟了,例如 1001-2000 丟了,然後 2001-3000,3001-4000 等後面的這幾個數據都順利到達,此時主機 B 反饋的 ACK 的確認的序號始終是 1001,如果主機 A 發現連續幾個 ACK 都是 1001, 主機 A 就知道 1001 這個數據報丟失,就會重傳 1001
當主機 B 收到 1001 這個數據的時候,由於剛纔到達 2001-7000 這些數據前面都已經收到了,接下倆 ACK 就從 7001 開始,重傳只是重傳丟失的數據,其他數據不需要額外重傳
5,流量控制
接收端處理數據的速度是有限的。如果發送端發的太快,導致接收端的緩衝區被打滿,這個時候如果發送端繼續發送,就會造成丟包,繼而引起丟包重傳等等一系列連鎖反應。因此 TCP 支持根據接收端的處理能力,來決定發送端的發送速度。這個機制就叫做
流量控制
(Flow Control);
接收端將自己可以接收的緩衝區大小放入 TCP 首部中的 "窗口大小" 字段,通過 ACK 端通知 發送端;
窗口大小字段越大,說明網絡的吞吐量越高;
接收端一旦發現自己的緩衝區快滿了,就會將窗口大小設置成一個更小的值通知給發送端;
發送端接受到這個窗口之後,就會減慢自己的發送速度;
如果接收端緩衝區滿了,就會將窗口置爲 0;這時發送方不再發送數據,但是需要定期發送 一個窗口探測數據段,使接收端把窗口大小告訴發送端。
流量控制本質上,是根據接受方的處理能力來制約發送方的發送效率
根據接受緩衝區的剩餘空間大小,來制約發送方的滑動窗口
通過 TCP 報頭中的窗口大小字段來反映給發送方
6,擁塞控制
雖然 TCP 有了滑動窗口這個大殺器,能夠高效可靠的發送大量的數據。但是如果在剛開始階段就發送大 量的數據,仍然可能引發問題。因爲網絡上有很多的計算機,可能當前的網絡狀態就已經比較擁堵。在不清楚當前網絡狀態下,貿然發送大量的數據,是很有可能引起雪上加霜的。TCP 引入 慢啓動機制,先發少量的數據,探探路,摸清當前的網絡擁堵狀態,再決定按照多大的速度傳 輸數據;
擁塞控制由於不好衡量傳輸路徑的擁堵情況,只能通過反覆試探的方式,逐漸試探出應該要用多大的窗口
-
1: 通過一個較小的窗口大小開始試探
-
2: 如果沒有發生擁堵(沒有丟包)就指數方式擴大擁塞窗口
-
3: 達到一定閾值後,線性增加窗口大小
-
4: 一直到出現丟包,窗口回到初始值,調整閾值爲出現丟包的窗口大小的一半
7,延遲應答
如果接收數據的主機立刻返回 ACK 應答,這時候返回的窗口可能比較小。
假設接收端緩衝區爲 1M。一次收到了 500K 的數據;如果立刻應答,返回的窗口就是 500K;
但實際上可能處理端處理的速度很快,10ms 之內就把 500K 數據從緩衝區消費掉了;
在這種情況下,接收端處理還遠沒有達到自己的極限,即使窗口再放大一些,也能處理過來;
如果接收端稍微等一會再應答,比如等待 200ms 再應答,那麼這個時候返回的窗口大小就是 1M;
窗口越大,網絡吞吐量就越大,傳輸效率就越高。我們的目標是在保證網絡不擁塞的情況 下儘量提高傳輸效率;
8,捎帶應答
在延時應答的基礎上,進一步提高程序運行效率而引入的機制
客戶端和服務器之間的通信模式一般都是 Requet-Response 模式,一問一答
主機 B 要給主機 A 返回兩個數據,嚴格的說,這兩個數據的傳輸時機是不一樣的
Req 發送內核收到數據,就會立刻返回 ACK,Resp 返回應用程序代碼,執行完 Resp 把響應寫回客戶端,才發送的響應
由於存在延時應答,ACK 的傳輸時機有延時,延時的時間足夠讓英語程序完成響應計算,應用程序
返回 Resp 的時候發現剛纔的 ACK 還沒發,就在 Resp 的基礎上,順便捎帶一個 ACK 的值
9,粘包問題
首先要明確,粘包問題中的 "包" ,是指的應用層的數據包。在 TCP 的協議頭中,沒有如同 UDP 一樣的 "報文長度" 這樣的字段,但是有一個序號這樣的字 段。站在傳輸層的角度,TCP 是一個一個報文過來的。按照序號排好序放在緩衝區中。站在應用層的角度,看到的只是一串連續的字節數據。那麼應用程序看到了這麼一連串的字節數據,就不知道從哪個部分開始到哪個部分,是一個 完整的應用層數據包。
那麼如何避免粘包問題呢?歸根結底就是一句話,明確兩個包之間的邊界。
對於定長的包,保證每次都按固定大小讀取即可;例如上面的 Request 結構,是固定大小 的,那麼就從緩衝區從頭開始按 sizeof(Request)依次讀取即可;對於變長的包,可以在包頭的位置,約定一個包總長度的字段,從而就知道了包的結束位 置;對於變長的包,還可以在包和包之間使用明確的分隔符(應用層協議,是程序猿自己來定 的,只要保證分隔符不和正文衝突即可)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KuOeO3rBfeKtlLR6dFQMHw