一、引言
TCP是一個面向連線的協議。無論哪一方向另一方傳送資料之前,都必須先在雙方之間建立一條連線。連線建立與終止的狀態變化圖如下:
二、三次握手建立連線
過程如下:- 客戶端傳送一個SYN資料包指明客戶端打算連線伺服器的埠,初始化序號(ISN)為m。
- 伺服器發回包含伺服器的ISN作為應答(值為n)。同時,將確認序號設定成客戶端ISN+1(m+1)來作為對客戶端SYN的確認。
- 客戶端傳送一個ACK資料包,ack=n+1,作為對伺服器的SYN的確認。
1.為什麼是三次握手,而不是兩次
網路是不可靠的,資料包是可能丟失的
。假設沒有第三次確認,客戶端向服務端傳送了 SYN,請求建立連線。由於延遲,服務端沒有及時收到這個包。於是客戶端重新傳送一個 SYN 包。回憶一下介紹 TCP 首部時提到的序列號,這兩個包的序列號顯然是相同的。假設服務端接收到了第二個 SYN 包,建立了通訊,一段時間後通訊結束,連線被關閉。這時候最初被髮送的 SYN 包剛剛抵達服務端,服務端又會傳送一次 ACK 確認。由於兩次握手就建立了連線,此時的服務端就會建立一個新的連線,然而客戶端覺得自己並沒有請求建立連線,所以就不會向服務端傳送資料。從而導致服務端建立了一個空的連線,白白浪費資源。
TCP是雙通道,需要雙向確定
。只有兩次握手,客戶端知道了伺服器收到了,伺服器不知道客戶端收到了,聯想打電話。通訊系統中的占拜庭將軍問題。
2.最大報文段長度
最大報文段長度(MSS)表示TCP傳往另一端的最大塊資料的長度。當一個連線建立時,連線的雙方都要通告各自的MSS。在三次握手的時候SYN的TCP首部中的可選欄位確定。乙太網的預設長度為1460。
3.
三、四次握手關閉連線(正常狀態)
建立一個連線需要三次握手,而終止一個連線要經過4次握手。這由TCP的半關閉(half-close)造成的。一個TCP連線是全雙工(即資料在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。
- 主動方想要關閉連線,傳送FIN包給被動方,序號為m
- 被動方接收到主動方傳送的FIN包,知道了對方要關閉連線,傳送ACK確認包,序號m+1。主動方連線關閉。
- 等待片刻(處於半關閉狀態),在此期間(fin_wait2,close_wait)。被動方傳送最後的資料,主動方接收最後的資料。
- 被動方確認要關閉連線,傳送FIN包。序號n。
- 主動方等待片刻(接收網路中,還未到達的資料包),傳送ACK確認包。序號n+1。到此連線關閉。
1.TCP的半關閉狀態
TCP提供了連線的一端在結束它的傳送後還能接收來自另一端資料的能力。如主動方處於fin_wait2狀態。
2.TIME_WAIT狀態
TIME_WAIT狀態也稱為2MSL等待狀態。每個具體TCP實現必須選擇一個報文段最大生存時間MSL( Maximum Segment Lifetime)。它是任何報文段被丟棄前在網路內的最長時間。因為TCP報文段以IP資料包在網路內傳輸,而IP資料包則有限制其生存時間的TTL欄位。在實際應用中,對 I P資料包TTL的限制是基於跳數,而不是定時器。 在處於2MSL等待狀態的socket(客戶端IP與埠,伺服器IP與埠)不能再被使用。但在實際的使用中,允許一個新的連線請求到達仍處於time_wait狀態的連線,只要新的序號大於該連線的前一個連線的最後序號。
四、正常狀態抓包
下面是一次完整的tcp建立連線,傳送資料,關閉連線過程
該過程為,3次握手建立連線,一次資料傳送,4次握手關閉連線
五、異常情況
出現異常的時候,伺服器通常通過復位報文來通告,復位報文為tcp資料包型別設定為rst。
1.連線超時或到達不存在的埠/伺服器
當伺服器端沒有開或網路問題,會出現連線超時的情況。抓包如下:
客戶端嘗試3三次來連線,有時候伺服器端會傳送rst資料包。2.異常終止一個連線
在TCP通訊中。如果通訊雙方應為某種原因(如突然斷電等)關閉連線時候一方(如A)沒有傳送fin資料包。另一端(如B)不知道對方已經關閉了連線。再次傳送資料的時候,異常關閉的一方,可能會返回一個rst資料包。通知異常關閉。如果一方已經關閉或異常終止連線而另一方卻還不知道,我們將這樣的TCP連線稱為半開啟(Half Open)的。
3.同時開啟
兩個應用程式同時彼此執行主動開啟的情況是可能的。每一方必須傳送一個SYN,且這些SYN必須傳遞給對方。這需要每一方使用一個對方熟知的埠作為本地埠。同時開啟的狀態遷移圖不同於正常狀態的三次握手,該情況下需要進行4次握手。如圖:
4.同時關閉
我們在以前討論過一方(通常但不總是客戶方)傳送第一個FIN執行主動關閉。雙方都執行主動關閉也是可能的,TCP協議也允許這樣的同時關閉(simultaneous close)。在同時關閉的時候,雙方都進入time_wait狀態,如圖:
六.TCP伺服器設計
大多數的TCP伺服器程式是併發的。當一個新的連線請求到達伺服器時,伺服器接受這個請求,並呼叫一個新程式來處理這個新的客戶請求。
1. 接入連線請求佇列
一個併發伺服器呼叫一個新的程式來處理每個客戶請求,因此處於被動連線請求的伺服器應該始終準備處理下一個呼入的連線請求。那正是使用併發伺服器的根本原因。但仍有可能出現當伺服器在建立一個新的程式時,或作業系統正忙於處理優先順序更高的程式時,到達多個連線請求。當伺服器正處於忙時,TCP是如何處理這些呼入的連線請求?TCP有這樣一個佇列來臨時存放這些連線-接入連線請求佇列。處理方式如下:
- 正等待連線請求的一端有一個固定長度的連線佇列,該佇列中的連線已被TCP接受(即三次握手已經完成),但還沒有被應用層所接受。注意區分TCP接受一個連線是將其放入這個佇列,而應用層接受連線是將其從該佇列中移出。
- 應用層將指明該佇列的最大長度,這個值通常稱為積壓值 (backlog)。
- 當一個連線請求(SYN)到達時, TCP使用一個演算法,根據當前連線佇列中的連線數來確定是否接收這個連線。積壓值說明的是TCP監聽的埠已被TCP接受而等待應用層接受的最大連線數。
- 如果對於新的連線請求,該TCP監聽的埠的連線佇列中還有空間,TCP模組將對SYN進行確認並完成連線的建立。此時,應用層不一定知道該新的連線,如果對方傳送資料,這些資料將放入緩衝佇列中。
- 如果對於新的連線請求,連線佇列中已沒有空間,TCP將不理會收到的SYN。也不發回任何報文段(即不發回 RST)。如果應用層不能及時接受已被TCP接受的連線,這些連線可能佔滿整個連線佇列,客戶的主動開啟最終將超時。
都看到這裡了,要不要掃二維碼關注一下微信公眾號林灣村龍貓。