1. 程式人生 > >深入理解TCP(2)TCP的斷開一定是四次揮手嗎?FIN_WAIT_2和CLOSE_WAIT,TIME_WAIT以及LAST_ACK的細節

深入理解TCP(2)TCP的斷開一定是四次揮手嗎?FIN_WAIT_2和CLOSE_WAIT,TIME_WAIT以及LAST_ACK的細節

答案是否定的

1我們回顧下使用wireshark的抓包

1.1. 伺服器未開 客戶端嘗試連線

這裡寫圖片描述

1.2 建立連線然後關閉,斷開的時候時候有時候三次握手有時候四次握手

這裡寫圖片描述
這裡寫圖片描述

1.3. 建立連線,互動一次然後斷開

這裡寫圖片描述

根據wireshark的包,四會握手的第二步 被動斷開的一方收到FIN(第一次握手)後要傳送ACK。但是抓的包中有時候會沒有這一步。
我們看一下一般的書中TCP四次揮手的狀態遷移圖

當被動斷開的一方傳送ACK的時候,被動斷開的
這裡寫圖片描述
對於這個圖大家很熟悉。
按照這個圖的順序說明會誤導大家以為斷開一個TCP總是這樣的。其實按照RFC793的描述將主動斷開的一方和被動斷開的一方分開描述會更清晰。

2. TCP斷開連線

2.1 主動斷開的一方

①首先發送FIN告訴對方我要關閉連線,自己進入到狀態FIN_WAIT_1此時開始不再處理和傳送應用層使用者的資料。
③收到對方對FINACK後自己進入到FIN_WAIT_2,這個時候依然可以接收對方的資料。如果收不到對方的ACK會再次傳送之前包含FIN在內的訊息。進入FIN_WAIT_2後就等待對方關閉,因為已經確認到對方收到自己的FIN了。
⑤收到對方的FIN說明被斷開的一方也要沒有資料傳送並關閉連線了,此時ACK對方的FIN進入TIME_WAIT。然後等2msl連線關閉。
需要注意的是,對方ACK自己的FIN後,並不會立即傳送FIN而是在應用層關閉連線後才會傳送FIN

2.2被動斷開的一方

②收到網路中傳來的FIN,自己ACK這個FIN然後進入到CLOSE_WAIT狀態。
④通知應用層要關閉連線(從這個角度上理解為什麼斷開比建立連線多了一次handshake,因為被斷開一方還有餘下的資料要傳送)這個時候等待使用者來回應CLOSE,在傳送FIN之前可以傳送自己餘下的資料。傳送FIN後進入LAST_ACK
⑧等待對方ACK自己的FIN**完成關閉。如果對方沒確認會再次傳送**FIN

2.3 斷開過程的一些解釋

(1)所以誰傳送FIN誰就不再發送資料了。
(2)傳送ACK的時候,ack是收到的req的值+1。因此第3和第4步的ack都是101。第四步並不是102。
(3)傳送的seq是對方傳送的ack所以第5步的seq是101而是不是102。
(4)第2步後主動斷開放進入FIN_WAIT是等待對方的FIN。此時不可以傳送資料但是可以接收資料。因此FIN意味著告訴對方“我沒有多餘的資料要傳送”。這也是為什麼斷開比建立連線多一次handshake的原因。因為傳送FIN後還可以接收資料。
(5)被斷開的一方收到FIN後,給對方傳送ACK

表示收到,自己狀態為CLOSE_WAIT。此時TCP連線處於半關閉狀態。被斷開的一方仍然可以傳送資料。這時候由上一層協議應用層來決定是否要傳送餘下的資料。

3. 為什麼四次揮手?

這裡寫圖片描述
我們看下斷開連線的部分
這裡寫圖片描述

3.1 實際中還會遇到的同時斷開的情況和三次握手的情況

3.1.1 同時斷開,同時傳送FIN

同時斷開的情況上面這張圖是rfc793官方圖。大家可以看到FIN_WAIT_1後還可能先收到FIN而不是ACK。why?因為兩端可以同時關閉。同時收到對方的FIN然後同時確認直接進入closing->time_wait->closed流程。

3.12 三次握手,傳送FIN後收到(FIN,ACK)

這張圖還缺少一個狀態轉換就是FIN_WAIT_1直接收到(FIN,ACK)後到達TIME_WAIT。這種情況是被斷開的一端沒有資料要傳送直接傳送了ACKFIN。這種情況通過抓包發現很常見。也就是四個過程也變為了三次握手。
這個還可能跟TCP的阻塞控制有關。

3.2 對於出現大量的CLOSE_WAIT

java       1042   root   42u  IPv4 826784      0t0  TCP iZ2zei0nwllapkwklisoncZ:38250->100.100.18.22:squid (CLOSE_WAIT)

這個階段對於被斷開的一方還沒有傳送FIN連線處於半連線狀態。但是如果CLOSE_WAIT的應用層並沒有進close(這是個主動操作)不進入LAST_ACK怎麼辦?

這種問題的出現舉個往往是程式設計師的程式碼有問題。出現異常沒有寫關閉連線。
但是依然可以vim /etc/sysctl.conf設定超時時間,引數是net.ipv4.tcp_keepalive_time預設是2個小時。

net.ipv4.tcp_keepalive_time=7200

3.3 出現大量的FIN_WAIT_2

主動斷開端此時等待FIN處於半連線狀態。對於主動斷開的一端而言,FIN_WAIT_1到FIN_WAIT_2是通過重發解決肯定回到FIN_WAIT_2但是FIN_WAIT_2到TIME_WAIT如果收不到對方的FIN怎麼辦?此不能重發,這個地方是通過超時解決
對於被斷開的一端而言,這種勤快TIME_WAIT(2msk)和LAST_ACK可以設定超時時間net.ipv4.tcp_fin_timeout

[[email protected] data]#  /sbin/sysctl -a | grep timeout 
net.ipv4.tcp_fin_timeout = 60

其中net.ipv4.tcp_fin_timeout就是FIN_TIME_WAIT2的超時時間可以在
vim /etc/sysctl.conf設定。

3.4 出現TIME_WAIT(2msl)出現,以及TIME_WAIT這個狀態的意義,msl是多長時間

TIME_WAIT狀態,必須在此狀態上停留兩倍的msl時間,等待2msl時間主要目的是怕最後一個 ACK包對方沒收到,那麼對方在超時後將重發第三次握手的FIN包,主動關閉端接到重發的FIN包後可以再發一個ACK應答包。在TIME_WAIT狀態 時兩端的埠不能使用,要等到2msl時間結束才可繼續使用。當連線處於2msl等待階段時任何遲到的報文段都將被丟棄。如果出現大量的TIME_WAIT。可以設定vim /etc/sysctl.conf來縮短msl的時間。總之TIME_WAIT到和LAST_ACK到達CLOSED之前收發兩端的埠都是不可用的。會佔用系統資源。
一個msl在rfc1112的建議是2分鐘,只是建議值

The TCP specification [TCP:1] arbitrarily
              assumes a value of 2 minutes for MSL

另外介紹個核心引數net.ipv4.tcp_max_tw_buckets = 5000能容納多少個TIME_WAIT超過後清除,以防止系統拖死。

3.5 LAST_ACK收不到ACK如何關閉

會重發FIN,這時候分兩種情況
(1)主動斷開的一方還在TIME_WAIT狀態中。這時候會發過來ACK, 被斷開的一方收到後順利從LAST_ACK進入到CLOSED。
(2)主動斷開的一方經過了2msl已經CLOSED了這時候會返回RST。被斷開的一方收到後也會進入CLOSED狀態。總之LAST_ACK總會進入到CLOSED狀態不需要超時機制。

4. 其他的核心引數

# 能容納多少個TIME_WAIT超過後清除,以防止系統拖死
net.ipv4.tcp_max_tw_buckets = 5000
#對於一個新建連線,核心要傳送多少個 SYN 連線請求才決定放棄
net.ipv4.tcp_syn_retries=6
#net.ipv4.tcp_synack_retries=2
#表示當keepalive起用的時候,TCP傳送keepalive訊息的頻度。預設是2小時
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_orphan_retries=3
#表示如果套接字由本端要求關閉,這個引數決定了它保持在FIN-WAIT-2狀態的時間
net.ipv4.tcp_fin_timeout=60  
#表示SYN佇列的長度,預設為1024,加大佇列長度為8192,可以容納更多等待連線的網路連線數。
net.ipv4.tcp_max_syn_backlog = 4096
#表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉
net.ipv4.tcp_syncookies = 1

#表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉
net.ipv4.tcp_tw_reuse = 1
#表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉
net.ipv4.tcp_tw_recycle = 1

##減少超時前的探測次數 
net.ipv4.tcp_keepalive_probes=5 
##優化網路裝置接收佇列 
net.core.netdev_max_backlog=3000