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狀態的維持時間。