TCP/IP

建立連接:
理解:窗口和滑動窗口
TCP的流量控制
TCP使用窗口機制進行流量控制
什麼是窗口?
連接建立時,各端分配一塊緩衝區用來存儲接收的數據,並將緩衝區的尺寸發送給另一端
接收方發送的確認信息中包含了自己剩餘的緩衝區尺寸
剩餘緩衝區空間的數量叫做窗口
2. TCP的流控過程(滑動窗口)

TCP(Transmission Control Protocol) 傳輸控制協議
三次握手
TCP是主機對主機層的傳輸控制協議,提供可靠的連接服務,採用三次握手確認建立一個連接:
位碼即tcp標誌位,有6種標示:
SYN(synchronous建立聯機)
ACK(acknowledgement 確認)
PSH(push傳送)
FIN(finish結束)
RST(reset重置)
URG(urgent緊急)
Sequence number(順序號碼)
Acknowledge number(確認號碼)
客戶端TCP狀態遷移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服務器TCP狀態遷移:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED

各個狀態的意義如下:
LISTEN - 偵聽來自遠方TCP端口的連接請求;
SYN-SENT -在發送連接請求後等待匹配的連接請求;
SYN-RECEIVED - 在收到和發送一個連接請求後等待對連接請求的確認;
ESTABLISHED- 代表一個打開的連接,數據可以傳送給用戶;
FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認;
FIN-WAIT-2 - 從遠程TCP等待連接中斷請求;
CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求;
CLOSING -等待遠程TCP對連接中斷的確認;
LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認;
TIME-WAIT -等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認;
CLOSED - 沒有任何連接狀態;

TCP/IP協議中,TCP協議提供可靠的連接服務,採用三次握手建立一個連接,如圖1所示。
(1)第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。
(2)第二次握手:服務器B收到SYN包,必須確認客戶A的SYN(ACK=j+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。
(3)第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包ACK(ACK=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據。
確認號:其數值等於發送方的 發送序號 +1 (即接收方期望接收的下一個序列號)。

圖1 TCP三次握手建立連接

TCP的包頭結構:
源端口 16位
目標端口 16位
序列號 32位
迴應序號 32位
TCP頭長度 4位
reserved 6位
控制代碼 6位
窗口大小 16位
偏移量 16位
校驗和 16位
選項 32位(可選)
這樣我們得出了TCP包頭的最小長度,爲20字節
第一次握手:
客戶端發送一個TCP的SYN標誌位置1的包指明客戶打算連接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段裏。

第二次握手:
服務器發回確認包(ACK)應答。即SYN標誌位和ACK標誌位均爲1同時,將確認序號(Acknowledgement Number)設置爲客戶的I S N加1以.即X+1。

第三次握手.
客戶端再次發送確認包(ACK) SYN標誌位爲0,ACK標誌位爲1.並且把服務器發來ACK的序號字段+1,放在確定字段中發送給對方.並且在數據段放寫ISN的+1
下面是具體的例子截圖:
1. 此圖包含兩部分信息: TCP 的三次握手 ( 方框中的內容) ( SYN, (SYN+ACK), ACK)

  1. TCP 的數據傳輸 ( [TCP segment of a reassembled PUD]) 可以看出, server 是將數據 TCP 層對消息包進行分片傳輸

(1)Server 端收到 HTTP 請求如 GET 之後,構造響應消息,其中攜帶網頁內容,在 server 端的 HTTP 層發送消息 200 OK->server 端的 TCP 層;

(2)server 端的 TCP 層對消息包進行分片傳輸;

(3)client 端的 TCP 層對接收到的各個消息包分片回送響應;

(4)client 端的 TCP 層每次收到一部分都會用 ACK 確認,之後 server 繼續傳輸, client 繼續確認,直到完成響應消息的所有分片之後, Server 發送組合 HTTP 響應包 200 O K ,此時在 client 端的消息跟蹤中纔可以顯示 HTTP 200 OK 的消息包

關閉連接:

由於 TCP 連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務後就能發送一個 FIN 來終止這個方向的連接。收到一個 FIN 只意味着這一方向上沒有數據流動,一個 TCP 連接在收到一個 FIN 後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

CP 的連接的拆除需要發送四個包,因此稱爲四次揮手 (four-way handshake) 。客戶端或服務器均可主動發起揮手動作,在 socket 編程中,任何一方執行 close() 操作即可產生揮手操作。

( 1 )客戶端 A 發送一個 FIN ,用來關閉客戶 A 到服務器 B 的數據傳送。

( 2 )服務器 B 收到這個 FIN ,它發回一個 ACK ,確認序號爲收到的序號加 1 。和 SYN 一樣,一個 FIN 將佔用一個序號。

( 3 )服務器 B 關閉與客戶端 A 的連接,發送一個 FIN 給客戶端 A 。

( 4 )客戶端 A 發回 ACK 報文確認,並將確認序號設置爲收到序號加 1 。
TCP 採用四次揮手關閉連接如圖 2 所示。
這裏寫圖片描述

圖2 TCP四次揮手關閉連接

參見wireshark抓包,實測的抓包結果並沒有嚴格按揮手時序。我估計是時間間隔太短造成。

深入理解TCP連接的釋放:

由於 TCP 連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個 FIN 來終止這個方向的連接。收到一個 FIN 只意味着這一方向上沒有數據流動,一個 TCP 連接在收到一個 FIN 後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

TCP 協議的連接是全雙工連接,一個 TCP 連接存在雙向的讀寫通道。

簡單說來是 “ 先關讀,後關寫 ” ,一共需要四個階段。以客戶機發起關閉連接爲例:

  1. 服務器讀通道關閉
  2. 客戶機寫通道關閉
  3. 客戶機讀通道關閉
  4. 服務器寫通道關閉

關閉行爲是在發起方數據發送完畢之後,給對方發出一個 FIN ( finish )數據段。直到接收到對方發送的 FIN ,且對方收到了接收確認 ACK 之後,雙方的數據通信完全結束,過程中每次接收都需要返回確認數據段 ACK 。

詳細過程:

第一階段 客戶機發送完數據之後,向服務器發送一個 FIN 數據段,序列號爲 i ;

  1. 服務器收到 FIN(i) 後,返回確認段 ACK ,序列號爲 i+1 ,關閉服務器讀通道;

  2. 客戶機收到 ACK(i+1) 後,關閉客戶機寫通道;
    (此時,客戶機仍能通過讀通道讀取服務器的數據,服務器仍能通過寫通道寫數據)
    第二階段 服務器發送完數據之後,向客戶機發送一個 FIN 數據段,序列號爲 j ;

  3. 客戶機收到 FIN(j) 後,返回確認段 ACK ,序列號爲 j+1 ,關閉客戶機讀通道;

  4. 服務器收到 ACK(j+1) 後,關閉服務器寫通道。

這是標準的 TCP 關閉兩個階段,服務器和客戶機都可以發起關閉,完全對稱。

FIN 標識是通過發送最後一塊數據時設置的,標準的例子中,服務器還在發送數據,所以要等到發送完的時候,設置 FIN (此時可稱爲 TCP 連接處於半關閉狀態,因爲數據仍可從被動關閉一方向主動關閉方傳送)。如果在服務器收到 FIN(i) 時,已經沒有數據需要發送,可以在返回 ACK(i+1) 的時候就設置 FIN(j) 標識,這樣就相當於可以合併第二步和第三步。讀《 Linux 網絡編程》關閉 TCP 連接章節,作以下筆記:
TCP 的 TIME_WAIT 和 Close_Wait 狀態

面試時看到應聘者簡歷中寫精通網絡, TCP 編程,我常問一個問題, TCP 建立連接需要幾次握手? 95% 以上的應聘者都能答對是 3 次。問 TCP 斷開連接需要幾次握手, 70% 的應聘者能答對是 4 次通訊。再問 CLOSE_WAIT , TIME_WAIT 是什麼狀態,怎麼產生的,對服務有什麼影響,如何消除?有一部分同學就回答不上來。不是我扣細節,而是在通訊爲主的前端服務器上,必須有能力處理各種 TCP 狀態。比如統計在本廠的一臺前端機上高峯時間 TCP 連接的情況,統計命令:

netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

結果:

除了ESTABLISHED,可以看到連接數比較多的幾個狀態是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就這幾個狀態的產生條件、對系統的影響以及處理方式進行簡單描述。

TCP狀態
TCP狀態如下圖所示:

可能有點眼花繚亂?再看看這個時序圖

下面看下大家一般比較關心的三種 TCP 狀態
SYN_RECV

服務端收到建立連接的 SYN 沒有收到 ACK 包的時候處在 SYN_RECV 狀態。有兩個相關係統配置:

1 , net.ipv4.tcp_synack_retries : INTEGER

默認值是 5

對於遠端的連接請求 SYN ,內核會發送 SYN + ACK 數據報,以確認收到上一個 SYN 連接請求包。這是所謂的三次握手 ( threeway handshake) 機制的第二個步驟。這裏決定內核在放棄連接之前所送出的 SYN+ACK 數目。不應該大於 255 ,默認值是 5 ,對應於 180 秒左右時間。通常我們不對這個值進行修改,因爲我們希望 TCP 連接不要因爲偶爾的丟包而無法建立。

2 , net.ipv4.tcp_syncookies
一般服務器都會設置 net.ipv4.tcp_syncookies=1 來防止 SYN Flood 攻擊。假設一個用戶向服務器發送了 SYN 報文後突然死機或掉線,那麼服務器在發出 SYN+ACK 應答報文後是無法收到客戶端的 ACK 報文的(第三次握手無法完成),這種情況下服務器端一般會重試(再次發送 SYN+ACK 給客戶端)並等待一段時間後丟棄這個未完成的連接,這段時間的長度我們稱爲 SYN Timeout ,一般來說這個時間是分鐘的數量級(大約爲 30 秒 -2 分鐘)。

這些處在 SYNC_RECV 的 TCP 連接稱爲半連接,並存儲在內核的半連接隊列中,在內核收到對端發送的 ack 包時會查找半連接隊列,並將符合的 requst_sock 信息存儲到完成三次握手的連接的隊列中,然後刪除此半連接。大量 SYNC_RECV 的 TCP 連接會導致半連接隊列溢出,這樣後續的連接建立請求會被內核直接丟棄,這就是 SYN Flood 攻擊。

能夠有效防範 SYN Flood 攻擊的手段之一,就是 SYN Cookie 。 SYN Cookie 原理由 D. J. Bernstain 和 Eric Schenk 發明。 SYN Cookie 是對 TCP 服務器端的三次握手協議作一些修改,專門用來防範 SYN Flood 攻擊的一種手段。它的原理是,在 TCP 服務器收到 TCP SYN 包並返回 TCP SYN+ACK 包時,不分配一個專門的數據區,而是根據這個 SYN 包計算出一個 cookie 值。在收到 TCP ACK 包時, TCP 服務器在根據那個 cookie 值檢查這個 TCP ACK 包的合法性。如果合法,再分配專門的數據區進行處理未來的 TCP 連接。

觀測服務上 SYN_RECV 連接個數爲: 7314 ,對於一個高併發連接的通訊服務器,這個數字比較正常。
CLOSE_WAIT

發起 TCP 連接關閉的一方稱爲 client ,被動關閉的一方稱爲 server 。被動關閉的 server 收到 FIN 後,但未發出 ACK 的 TCP 狀態是 CLOSE_WAIT 。出現這種狀況一般都是由於 server 端代碼的問題,如果你的服務器上出現大量 CLOSE_WAIT ,應該要考慮檢查代碼。
TIME_WAIT

根據 TCP 協議定義的 3 次握手斷開連接規定 , 發起 socket 主動關閉的一方 socket 將進入 TIME_WAIT 狀態。 TIME_WAIT 狀態將持續 2 個 MSL(Max Segment Lifetime), 在 Windows 下默認爲 4 分鐘,即 240 秒。 TIME_WAIT 狀態下的 socket 不能被回收使用 . 具體現象是對於一個處理大量短連接的服務器 , 如果是由服務器主動關閉客戶端的連接,將導致服務器端存在大量的處於 TIME_WAIT 狀態的 socket , 甚至比處於 Established 狀態下的 socket 多的多 , 嚴重影響服務器的處理能力,甚至耗盡可用的 socket ,停止服務。

爲什麼需要 TIME_WAIT ? TIME_WAIT 是 TCP 協議用以保證被重新分配的 socket 不會受到之前殘留的延遲重發報文影響的機制 , 是必要的邏輯保證。

和 TIME_WAIT 狀態有關的系統參數有一般由 3 個,本廠設置如下:
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_fin_timeout ,默認 60s ,減小 fin_timeout ,減少 TIME_WAIT 連接數量。

net.ipv4.tcp_tw_reuse = 1 表示開啓重用。允許將 TIME-WAIT sockets 重新用於新的 TCP 連接,默認爲 0 ,表示關閉;
net.ipv4.tcp_tw_recycle = 1 表示開啓 TCP 連接中 TIME-WAIT sockets 的快速回收,默認爲 0 ,表示關閉。

爲了方便描述,我給這個 TCP 連接的一端起名爲 Client ,給另外一端起名爲 Server 。上圖描述的是 Client 主動關閉的過程, FTP 協議中就這樣的。如果要描述 Server 主動關閉的過程,只要交換描述過程中的 Server 和 Client 就可以了, HTTP 協議就是這樣的。

描述過程:
Client 調用 close() 函數,給 Server 發送 FIN ,請求關閉連接; Server 收到 FIN 之後給 Client 返回確認 ACK ,同時關閉讀通道(不清楚就去看一下 shutdown 和 close 的差別),也就是說現在不能再從這個連接上讀取東西,現在 read 返回 0 。此時 Server 的 TCP 狀態轉化爲 CLOSE_WAIT 狀態。

Client 收到對自己的 FIN 確認後,關閉 寫通道,不再向連接中寫入任何數據。

接下來 Server 調用 close() 來關閉連接,給 Client 發送 FIN , Client 收到後給 Server 回覆 ACK 確認,同時 Client 關閉讀通道,進入 TIME_WAIT 狀態。

Server 接收到 Client 對自己的 FIN 的確認 ACK ,關閉寫通道, TCP 連接轉化爲 CLOSED ,也就是關閉連接。

Client 在 TIME_WAIT 狀態下要等待最大數據段生存期的兩倍,然後才進入 CLOSED 狀態, TCP 協議關閉連接過程徹底結束。

以上就是 TCP 協議關閉連接的過程,現在說一下 TIME_WAIT 狀態。

從上面可以看到,主動發起關閉連接的操作的一方將達到 TIME_WAIT 狀態,而且這個狀態要保持 Maximum Segment Lifetime 的兩倍時間。爲什麼要這樣做而不是直接進入 CLOSED 狀態?

原因有二:

一、保證 TCP 協議的全雙工連接能夠可靠關閉

二、保證這次連接的重複數據段從網絡中消失

先說第一點,如果 Client 直接 CLOSED 了,那麼由於 IP 協議的不可靠性或者是其它網絡原因,導致 Server 沒有收到 Client 最後回覆的 ACK 。那麼 Server 就會在超時之後繼續發送 FIN ,此時由於 Client 已經 CLOSED 了,就找不到與重發的 FIN 對應的連接,最後 Server 就會收到 RST 而不是 ACK , Server 就會以爲是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致 TCP 協議不符合可靠連接的要求。所以, Client 不是直接進入 CLOSED ,而是要保持 TIME_WAIT ,當再次收到 FIN 的時候,能夠保證對方收到 ACK ,最後正確的關閉連接。

再說第二點,如果 Client 直接 CLOSED ,然後又再向 Server 發起一個新連接,我們不能保證這個新連接與剛關閉的連接的端口號是不同的。也就是說有可能新連接和老連接的端口號是相同的。一般來說不會發生什麼問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接端口號是一樣的,如果前一次連接的某些數據仍然滯留在網絡中,這些延遲數據在建立新連接之後纔到達 Server ,由於新連接和老連接的端口號是一樣的,又因爲 TCP 協議判斷不同連接的依據是 socket pair ,於是, TCP 協議就認爲那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以 TCP 連接還要在 TIME_WAIT 狀態等待 2 倍 MSL ,這樣可以保證本次連接的所有數據都從網絡中消失。

各種協議都是前人千錘百煉後得到的標準,規範。從細節中都能感受到精巧和嚴謹。每次深入都有同一個感覺,精妙。

發佈了28 篇原創文章 · 獲贊 10 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章