tcp關閉鏈接不區分客戶端和服務端,哪一端口能夠主動發起關閉鏈接請求。因此爲了描述方便,描述中的「主動方」表示主動發起關閉鏈接一方,「被動方」表示被動關閉鏈接一方。java
1. tcp關閉鏈接狀態轉換tomcat
上圖是tcp鏈接主動關閉端的狀態轉換圖:服務器
(1)應用層調用close函數發起關閉鏈接請求網絡
(2)發送FIN到對端,關閉寫通道,本身進入FIN_WAIT1狀態併發
(3)等待對端的確認ACK到來,接受到ACK後進入FIN_WAIT2狀態;若是在超時時間內沒有收到確認ACK直接進入CLOSED狀態socket
(4)若是在FIN_WAIT1狀態時收到了對端的FIN則進入CLOSING狀態(雙發都發出了關閉鏈接請求)tcp
(5)在FIN_WAIT2接受到了對端FIN後進入TIME_WAIT狀態;若是在超時時間內沒有收這個FIN則直接進入CLOSED狀態函數
(6)在TIME_WAIT狀態等待2個MSL(2個報文最長存活週期)後進入CLOSED狀態操作系統
上圖是tcp鏈接被動關閉方的狀態轉換圖對象
(1)收到對端FIN後,關閉讀通道進入CLOSE_WAIT狀態
(2)在CLOSE_WAIT狀態等待應用層調用close函數關閉鏈接
(3)若是在超時時間內調用了close,則進入LAST_ACK狀態;不然直接進入CLOSED狀態
(4)在LAST_ACK狀態,發送FIN到對端並等待對端的確認ACK
(5)若是在超時時間內收到了確認ACK則進入CLOSED狀態,不然直接進入CLOSED狀態
2. 狀態分析
2.1 FIN_WAIT1
這個狀態在實際的工做中不多見。主動方調用close函數關閉鏈接後馬上進入FIN_WAIT1狀態,此時只要收到對端確認ACK後立刻會進入FIN_WAIT2狀態。由於對端確認ACK是TCP協議棧本身控制的,因此很快就會發出。出現場景:主動方等待ACK過程當中網絡斷掉了,致使長時間收不到ACK,主動方就會停留在CLOSE_WAIT1狀態上(超時時間:通常默認60s超時)。此時咱們可使用netstat -anpt 命令看到這種狀態。
NOTE:這個狀態若是超時,將直接進入CLOSED狀態。關於超時時間你可能須要瞭解:tcp_fin_timeout這個配置參數。
2.2 FIN_WAIT2
這個狀態比較常見。主動端在等待對端FIN到來過程當中,會一你直保持這個狀態(超時時間:通常默認是60s)。因爲網絡中斷,或者對端很忙還沒來得及發送FIN、或者對端有bug忘記關閉鏈接等都會致使主動端長時間處於FIN_WAIT2狀態。若是主動方發現大量FIN_WAIT2狀態時,應該引發相關人員的注意,這多是網絡不穩、對端程序bug的表現。
NOTE:這個狀態若是超時,將直接進入CLOSED狀態。關於超時時間你可能須要瞭解:tcp_fin_timeout這個配置參數。
2.3 TIME_WAIT
這個狀態最多見。主動方收到對端的FIN後進入TIME_WAIT狀態。而後發送最後一個確認ACK到對端。以後等待2個最大的報文存活週期,正常的關閉流程客戶端TCP鏈接都會通過這個狀態,最終進入CLOSED狀態。因此咱們使用netstat -anpt命令發現客戶端有不少的TIME_WAIT,通常這是正常的現象。
NOTE:
* 這個狀態的鏈接尚未真正關閉,因此佔用的文件句柄和端口號等資源也沒有釋放。若是TIME_WAIT狀態的鏈接過多,將會致使文件句柄或者端口號等資源不足。若是你想控制這個狀態鏈接的數量,你可能須要詳細瞭解:tcp_max_tw_buckets tcp_tw_recycle tcp_tw_reuse三個系統配置。
* 對於有大量短連接服務的服務器來講,服務端主動關閉鏈接會產生大量的TIME_WAIT,若是不及時回收這些個鏈接會形成資源嚴重浪費。不過一個應用層系統都有本身最大併發鏈接數限制、操做系統也有最大TIME_WAIT鏈接數限制。因此通常也不會產生大的問題。
* 等待2MSL的緣由:
(1)保證殘留網絡報不會被新鏈接接收而產生數據錯亂。因爲本身上一次發送的數據包可能還殘留在網絡中,等待2MSL時間能夠保證全部殘留的網絡報在本身關閉前都已經超時。
(2)確保本身最後ACK發到對端。由於ACK發送也可能會失敗,這是對端會從新發送FIN,若是已經CLOSED了那麼對端將收到RST而不是ACK了,這不符合TCP可靠關閉策略。
2.4 CLOSING
雙發幾乎同時都調用了close接口主動關閉鏈接,此時都進入了FIN_WAIT1狀態。若是在FIN_WAIT1狀態指望收到對方的ACK但卻收到了對方的FIN,這時候雙方都進入CLOSING狀態。而後都給對方一個ACK確認,收到了ACK後就會進入CLOSED狀態了。
NOTE:這個狀態若是超時,將直接進入CLOSED狀態。關於這個超時時間的設置我暫時尚未找到?
2.5 CLOSE_WAIT(重點說明下這個狀態)
這個狀態代表TCP鏈接等待被關閉。只可能在被動方出現。若是被動方存在大量的CLOSE_WAIT狀態須要由於咱們的特別注意了。咱們要仔細研究確認爲何被動方遲遲不肯關閉鏈接(或許是咱們程序中的bug開啓了鏈接,用完後卻忘記關閉)
目前開發過程當中遇到以下這個場景緻使被動方有不少的CLOSE_WAIT狀態:
A是一個應用程序,B是一個tomcat服務器
A開了一個鏈接Conn,發送請求給B
A接受相應數據後沒有調用Conn.close關閉鏈接,在A端垃圾回收這些Conn對象前,這些鏈接一直保持着
B端的鏈接超時後會主動發起關閉鏈接請求給A,此時A進入了CLOSE_WAIT狀態,B進入了FIN_WAIT2狀態,因爲A遲遲不發送FIN給B,B端觸發timeout直接進入了CLOSED狀態。
這樣一個場景B端因爲有超時設置一個爲60s,不會存在大量的FIN_WAIT2狀態
可是A端就會殘留大量的CLOSE_WAIT狀態(CLOSE_WAIT狀態也有超時,可是太大,默認爲43200s,詳情見tcp_timeout_close_wait系統配置)。還好A端的java虛擬機的最大對內存配置較小,因爲CLOSE_WAIT狀態鏈接一樣佔用了內存資源,數量不少後就會觸發垃圾回收,此時A端的CLOSE_WAIT的鏈接Conn對象就會被銷燬了(同時內存和句柄、端口等資源也被釋放了)
很明顯這是一個bug致使的問題,若是沒有及時回收的話,就會把內存、句柄或者端口等資源給用完,致使程序crash掉。
2.6 LAST_ACK
這個狀態只可能在被動端出現。當被動端調用close接口關閉鏈接後便會進入這個狀態,同時發送一個FIN給對端。在接受對端的ACK確認後便會進入CLOSED狀態,這個狀態通常不易出現,除非網絡中斷,通常對端會很快給與響應的。
2.7 狀態總結
主動端可能出現的狀態:FIN_WAIT一、FIN_WAIT二、CLOSING、TIME_WAIT
被動端可能出現的狀態:CLOSE_WAIT LAST_ACK
敘述中提到的超時時間在個人另外一片文章tcp鏈接超時那點事中有具體的分析
NOTE:
(1)主動端出現大量的FIN_WAIT1時須要注意網絡是否暢通、出現大量的FIN_WAIT2須要仔細檢查程序爲什麼遲遲收不到對端的FIN(多是主動方或者被動方的bug)、出現大量的TIME_WAIT須要注意系統的併發量/socket句柄資源/內存使用/端口號資源等。
(2)被動端出現大量的 CLOSE_WAIT 須要仔細檢查爲什麼本身遲遲不肯調用close關閉鏈接(多是bug,socket打開用完沒有關閉)