一:摘要概述
相見時難別亦難,東風無力百花殘。經歷三次握手順利會師,MTU與MSS對數據包大小限制,滑動窗口對於發送端流量控制,擁塞控制對網絡狀態的控制,以及三次握手過程中的連接隊列詳解。最後就是塵歸塵土歸土,來到了釋放連接說再見的時刻。本文將詳細闡述四次揮手的過程、狀態變化以及是否可以不需要四次揮手的一些驗證!本文中很多圖是來源於張師傅的掘金小冊,已經和作者聯繫同意,大家有興趣可以購買改小冊仔細閱讀,獲益匪淺
二:四次揮手過程
下面過程描述中將主動關閉方定義爲A、被動關閉方定義爲B
- A發送FIN包申請關閉連接,此時A進入
FIN-WAIT-1
狀態,不會再向B端發送數據包 - B接收到FIN包後會立即回覆ACK包,此時B進行
CLOSE-WAIT
狀態 - A接收到B傳輸的ACK確認包後會將其狀態修改爲
FIN-WAIT-2
- B檢查自身是否還有數據需要發送,如果有將會發送完所有未發送數據,若無則發送FIN包進入
LAST-ACK
狀態 - A接收到B的FIN包後會發送ACK包,進入
TIME-WAIT
狀態等待 - 兩個MSL時鐘後A將會釋放連接
CLOSED
- B收到A傳輸的ACK確認包後將會直接釋放連接進入
CLOSED
狀態
三:四次揮手模擬
--tolerance_usecs=1000000
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 65535 <mss 100>
+0 > S. 0:0(0) ack 1 <...>
.1 < . 1:1(0) ack 1 win 65535
+.1 accept(3, ..., ...) = 4
// 服務端主動斷開連接
+.1 close(4) = 0
+0 > F. 1:1(0) ack 1 <...>
// 向協議棧注入 ACK 包,模擬客戶端發送了 ACK
+.1 < . 1:1(0) ack 2 win 257
// 向協議棧注入 FIN,模擬服務端收到了 FIN
+.1 < F. 1:1(0) win 65535 <mss 100>
+0 `sleep 1000000`
可以看到圖中紅框部分,其過程如下:
- 服務端發送FIN包斷開連接
- 客戶端回覆ACK
- 客戶端再次回覆FIN
- 服務端回覆ACK
四:四次次揮次數
四次揮手一定是四次?這是經常會出現的靈魂拷問,你會說一大堆理由支撐這個觀點。其實最主要還是在於被動關閉方需要檢查是否還有數據需要進行傳輸,所以將FIN與ACK進行了兩次發包。這就比三次握手多了一環,回想三次握手時SYN+ACK是一個包進行發送。但是這個思考一下,如果沒有數據發送的時候會不會將FIN+ACK包一起進行發送?看如下截圖驗證:
上一篇文章剛講到了延遲確認的機制,請仔細回憶。TCP(六) – 重傳與確認
五:TIME-WAIT狀態
主動關閉方往往需要等待2MSL時間才能關閉進入CLOSED狀態,那麼問題來了,等待2MSL時間的TIME-WAIT狀態到底有什麼用?爲什麼會這麼設計?
當存在網絡延遲的時候往往可能就會有姍姍來遲,如上圖所示。如果這時候立即進入CLOSED狀態,且正好有連接使用的四元組件相同,就會導致新的連接接收到舊連接的數據包。總得來講就是讓舊連接數據包消失在網絡傳輸中
除了消除掉網絡中舊連接的數據包之外,還有一個原因就是充分保證ACK包的可達。當ACK包丟失時若接收到服務端超時重傳的FIN包可以回覆ACK包使得服務端進入CLOSED狀態
六:timestamp
請求時間戳,這是一個增量的數據,與系統時間戳並沒有關係。需要客戶端與服務端同時打開纔會生效,打開參數位置位於/proc/sys/net/ipv4/tcp_timestamp
,值爲1是打開狀態、0關閉
- TSval存儲本次請求的增量時間戳
- Tsecr存儲數據發送方上次的時間戳
七:tcp_tw_reuse
當系統中存在大量TIME_WAIT狀態連接時無疑是一種極其嚴重的浪費行爲,所以對於TIME-WAIT狀態的連接希望可以進行復用。系統中提供了參數/proc/sys/net/ipv4/tcp_tw_reuse
參數,可以對TIME-WAIT的參數進行重複使用,其原理如下所示:
- 如果有網絡波動導致的舊連接包迷路,新連接判斷到該包攜帶的時間戳小於當前記錄時間戳時就會將其拋棄
- 如果因爲ACK丟包,被動關閉方重傳FIN+ACK時,主動關閉方也使用時間戳判斷到該現象即可回覆RST斷開連接
相對於參數tcp_tw_reuse來講還有一個更加激進的參數tcp_tw_recyle,該參數會緩存所有IP創建連接的時間戳,會丟棄調所有比該時間戳小的數據包。這樣的策略在NAT網絡中就是爆炸性的操作,所以這個參數都是直接將其關閉
八:SO_REUSEADDR
經常會發現一個問題,當服務端程序崩潰的時候重啓服務發現提示你該地址已經被佔用,what?其實這時候就是TIME-WAIT在搞鬼。如果程序崩潰重啓要等待2MSL時間,這肯定是不合理的,所以可以設置參數SO_REUSEADDR,允許對處於TIME-WAIT狀態連接的端口進行綁定。這個參數一般會在服務端進行設置,因爲客戶端的連接端口基本都是動態的,但是服務端都是固定監聽某個端口
九:SO_LINGER
當關閉連接時默認的都是發送完所有數據緩衝區的數據後才發送FIN包進行四次揮手斷開連接,但是如果不想等待這麼久直接關閉連接應該怎麼辦?系統提供SO_LINGER參數,該參數開啓後提供兩個參數,如下所示:
- l_onoff: 0表示禁用,非0表示啓動SO_LINGER
- l_linger:當l_onoff參數非0表示啓用時。該參數爲0表示直接丟棄所有緩衝區數據,併發送RST斷開連接。非0表示給與數值時間,時間內不論是否發送完數據都將發送RST包斷開連接