關於TCP串連建立與終止那點事,tcp串連終止
0. 前言
最近在處理公司遺留項目的時候發現自己對TCP協議一點都不懂,所以補了點關於TCP串連的建立和終止的內容,這裡簡單寫下自己瞭解的部分,省略了報文序號確認序號這些無關的欄位,主要討論TCP狀態的轉換以及Linux下的一些問題。
對於這篇文章來說,主要是記錄自己遇到的一些問題以及學習到的一些東西。
關於TCP/IP協議,這裡推薦一本書:《TCP/IP協議詳解:卷1》
1. TCP串連的建立
學過電腦網路的都知道TCP串連的建立需要三向交握,當時在大學也這麼聽著,但是具體怎麼三次還是最近補了才知道這麼回事。
對於用戶端/伺服器模型來說:
1. 首先用戶端發起串連(發送SYN報文,進入SYN_SENT狀態)
2. 伺服器接收到SYN,然後響應(發送SYN ACK,進入SYN_RCVD狀態,注意這個時候串連並沒有建立)
3. 用戶端接受到後,發送確認報文(發送ACK,進入Established狀態)
4. 伺服器收到後才進入鏈路建立(Established)狀態。
如(圖為百度搜出來的)
2. TCP串連的終止
對於TCP串連的終止來說,卻需要四次揮手來完成,這裡以客戶/伺服器模型,用戶端主動發起關閉來說明(這裡伺服器也可以發起主動發起關閉)。
1. 用戶端發送FIN(進入FIN_WAIT_1狀態)
2. 伺服器接收到FIN後對發送ACK確認(此時伺服器進入CLOSE_WAIT狀態,用戶端接受到該ACK後由FIN_WAIT_1 轉為FIN_WAIT_2狀態)
3. 伺服器調用close發送FIN(進入LASK_ACK狀態)
4. 用戶端接收到伺服器發送的FIN後發送ACK(進入TIME_WAIT狀態)
5. 伺服器接收到後結束(LASK_ACK狀態轉為虛擬CLOSED,即已經沒有狀態了)
如所示:
3. TCP狀態轉換
其實如果看懂了上面串連的建立以及終止的話,很容易就可以看懂下面的狀態轉換圖,上面兩個就可以看做由它拆解出來的。
4. 遇到CLOSE_WAIT狀態的一些情況
前面說了,最近在維護一些曆史遺留的項目,那代碼簡直是叼炸天了,直接使用兩個進程共用一個select,在accept前加上檔案鎖,select只監聽伺服器端fd,其它fd不監聽....(此處省略一千字),然後現在出現了大量的CLOSE_WAIT狀態,我在想難道以前就沒出現過?奇葩。不過被DDoS攻擊有些也會出現這種現象。最後討論了一下午說怎麼樣才能少改動原來的代碼....最後老大拍板,重寫select部分~^~,使用epoll實現。
上面的狀態圖可以看出,伺服器由於接收到用戶端發來的FIN,會進入CLOSE_WAIT,如果此時沒有監聽該用戶端fd並且沒有調用close,那麼這時會導致佔用的FD沒有被釋放,資源就這麼被泄漏掉了,這樣也會導致存在大量的CLOSE_WAIT狀態,以至於後續FD消耗完了的時候(一般系統預設1024),accept就會失敗。(註:這裡CLOSE_WAIT狀態是由於應用程式沒有調用close導致的,系統不會釋放該資源,即會一直存在)
(註:即使accept失敗,但是對於鏈路來說,還是能建立成功的,因為對於Linux TCP底層實現來說,存在兩個隊列,一個為半鏈路隊列,另一個為三向交握成功但是沒有被伺服器accept取走的連結的隊列。)
後續對於Linux下的伺服器來說一般都是使用epoll來處理,效率比select高。
5. 補充
這裡看到一些部落格說通過設定系統參數來改變CLOSE_WAIT的維持等待時間,我查閱了一下,並且也嘗試過(其實不用試)。不能夠通過設定系統參數來更改的,因為這時應用程式內部導致的,,而且根本就不存在說CLOSE_WAIT維持時間一說,該狀態只有應用程式調用close才會轉為其它狀態(或者關閉應用程式),這裡說通過修改系統參數一般指的時TIME_WAIT狀態的維持時間。