TCP原理(三次握手四次揮手)
VSole2022-04-28 22:39:02
原文: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)依次讀取即可;對于變長的包,可以在包頭的位置,約定一個包總長度的字段,從而就知道了包的結束位 置;對于變長的包,還可以在包和包之間使用明確的分隔符(應用層協議,是程序猿自己來定 的,只要保證分隔符不和正文沖突即可)
VSole
網絡安全專家