TCP鏈接狀態詳解及TIME_WAIT過多的解決方法

 

TIME_WAIT狀態原理html

----------------------------java

通訊雙方創建TCP鏈接後,主動關閉鏈接的一方就會進入TIME_WAIT狀態。nginx

客戶端主動關閉鏈接時,會發送最後一個ack後,而後會進入TIME_WAIT狀態,再停留2個MSL時間(後有MSL的解釋),進入CLOSED狀態。後端

下圖是以客戶端主動關閉鏈接爲例,說明這一過程的。安全

 

TIME_WAIT狀態存在的理由服務器

----------------------------cookie

TCP/IP協議就是這樣設計的,是不可避免的。主要有兩個緣由:網絡

1)可靠地實現TCP全雙工鏈接的終止併發

TCP協議在關閉鏈接的四次握手過程當中,最終的ACK是由主動關閉鏈接的一端(後面統稱A端)發出的,若是這個ACK丟失,對方(後面統稱B端)將重發出最終的FIN,所以A端必須維護狀態信息(TIME_WAIT)容許它重發最終的ACK。若是A端不維持TIME_WAIT狀態,而是處於CLOSED 狀態,那麼A端將響應RST分節,B端收到後將此分節解釋成一個錯誤(在java中會拋出connection reset的SocketException)。app

於是,要實現TCP全雙工鏈接的正常終止,必須處理終止過程當中四個分節任何一個分節的丟失狀況,主動關閉鏈接的A端必須維持TIME_WAIT狀態 。

 

2)容許老的重複分節在網絡中消逝 

TCP分節可能因爲路由器異常而「迷途」,在迷途期間,TCP發送端可能因確認超時而重發這個分節,迷途的分節在路由器修復後也會被送到最終目的地,這個遲到的迷途分節到達時可能會引發問題。在關閉「前一個鏈接」以後,立刻又從新創建起一個相同的IP和端口之間的「新鏈接」,「前一個鏈接」的迷途重複分組在「前一個鏈接」終止後到達,而被「新鏈接」收到了。爲了不這個狀況,TCP協議不容許處於TIME_WAIT狀態的鏈接啓動一個新的可用鏈接,由於TIME_WAIT狀態持續2MSL,就能夠保證當成功創建一個新TCP鏈接的時候,來自舊鏈接重複分組已經在網絡中消逝。

MSL時間

----------------------------

MSL就是maximum segment lifetime(最大分節生命期),這是一個IP數據包能在互聯網上生存的最長時間,超過這個時間IP數據包將在網絡中消失 。MSL在RFC 1122上建議是2分鐘,而源自berkeley的TCP實現傳統上使用30秒。

 

TIME_WAIT狀態維持時間

----------------------------

TIME_WAIT狀態維持時間是兩個MSL時間長度,也就是在1-4分鐘。Windows操做系統就是4分鐘。

http://www.cnblogs.com/itcomputer/p/7150954.html

 

 

上圖對排除和定位網絡或系統故障時大有幫助,可是怎樣緊緊地將這張圖刻在腦中呢?那麼你就必定要對這張圖的每個狀態,及轉換的過程有深入地認識,不能只停留在只知其一;不知其二之中。下面對這張圖的11種狀態詳細解釋一下,以便增強記憶!不過在這以前,先回顧一下TCP創建鏈接的三次握手過程,以及關閉鏈接的四次握手過程。

一、創建鏈接協議(三次握手)
(1)客戶端發送一個帶SYN標誌的TCP報文到服務器。這是三次握手過程當中的報文1。

(2) 服務器端迴應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標誌和SYN標誌。所以它表示對剛纔客戶端SYN報文的迴應;同時又標誌SYN給客戶端,詢問客戶端是否準備好進行數據通信。

(3) 客戶必須再次迴應服務段一個ACK報文,這是報文段3。

二、鏈接終止協議(四次握手)
因爲TCP鏈接是全雙工的,所以每一個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的鏈接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP鏈接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另外一方執行被動關閉。

(1) TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段4)。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1(報文段5)。和SYN同樣,一個FIN將佔用一個序號。
(3) 服務器關閉客戶端的鏈接,發送一個FIN給客戶端(報文段6)。
(4) 客戶段發回ACK報文確認,並將確認序號設置爲收到序號加1(報文段7)。

CLOSED: 這個沒什麼好說的了,表示初始狀態。

LISTEN: 這個也是很是容易理解的一個狀態,表示服務器端的某個SOCKET處於監聽狀態,能夠接受鏈接了。

SYN_RCVD: 這個狀態表示接受到了SYN報文,在正常狀況下,這個狀態是服務器端的SOCKET在創建TCP鏈接時的三次握手會話過程當中的一箇中間狀態,很短暫,基本上用netstat你是很難看到這種狀態的,除非你特地寫了一個客戶端測試程序,故意將三次TCP握手過程當中最後一個ACK報文不予發送。所以這種狀態時,當收到客戶端的ACK報文後,它會進入到ESTABLISHED狀態。

SYN_SENT: 這個狀態與SYN_RCVD遙想呼應,當客戶端SOCKET執行CONNECT鏈接時,它首先發送SYN報文,所以也隨即它會進入到了SYN_SENT狀態,並等待服務端的發送三次握手中的第2個報文。SYN_SENT狀態表示客戶端已發送SYN報文。

ESTABLISHED:這個容易理解了,表示鏈接已經創建了。

FIN_WAIT_1: 這個狀態要好好解釋一下,其實FIN_WAIT_1和FIN_WAIT_2狀態的真正含義都是表示等待對方的FIN報文。而這兩種狀態的區別是:FIN_WAIT_1狀態其實是當SOCKET在ESTABLISHED狀態時,它想主動關閉鏈接,向對方發送了FIN報文,此時該SOCKET即進入到FIN_WAIT_1狀態。而當對方迴應ACK報文後,則進入到FIN_WAIT_2狀態,固然在實際的正常狀況下,不管對方何種狀況下,都應該立刻迴應ACK報文,因此FIN_WAIT_1狀態通常是比較難見到的,而FIN_WAIT_2狀態還有時經常能夠用netstat看到。

FIN_WAIT_2:上面已經詳細解釋了這種狀態,實際上FIN_WAIT_2狀態下的SOCKET,表示半鏈接,也即有一方要求close鏈接,但另外還告訴對方,我暫時還有點數據須要傳送給你,稍後再關閉鏈接。

TIME_WAIT: 表示收到了對方的FIN報文,併發送出了ACK報文,就等2MSL後便可回到CLOSED可用狀態了。若是FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,能夠直接進入到TIME_WAIT狀態,而無須通過FIN_WAIT_2狀態。

注:MSL(最大分段生存期)指明TCP報文在Internet上最長生存時間,每一個具體的TCP實現都必須選擇一個肯定的MSL值.RFC 1122建議是2分鐘,但BSD傳統實現採用了30秒.TIME_WAIT 狀態最大保持時間是2 * MSL,也就是1-4分鐘.

CLOSING: 這種狀態比較特殊,實際狀況中應該是不多見,屬於一種比較罕見的例外狀態。正常狀況下,當你發送FIN報文後,按理來講是應該先收到(或同時收到)對方的ACK報文,再收到對方的FIN報文。可是CLOSING狀態表示你發送FIN報文後,並無收到對方的ACK報文,反而卻也收到了對方的FIN報文。什麼狀況下會出現此種狀況呢?其實細想一下,也不可貴出結論:那就是若是雙方几乎在同時close一個SOCKET的話,那麼就出現了雙方同時發送FIN報文的狀況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET鏈接。

CLOSE_WAIT: 這種狀態的含義實際上是表示在等待關閉。怎麼理解呢?當對方close一個SOCKET後發送FIN報文給本身,你係統毫無疑問地會迴應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正須要考慮的事情是察看你是否還有數據發送給對方,若是沒有的話,那麼你也就能夠close這個SOCKET,發送FIN報文給對方,也即關閉鏈接。因此你在CLOSE_WAIT狀態下,須要完成的事情是等待你去關閉鏈接。

LAST_ACK: 這個狀態仍是比較容易好理解的,它是被動關閉一方在發送FIN報文後,最後等待對方的ACK報文。當收到ACK報文後,也便可以進入到CLOSED可用狀態了。

最後有2個問題的回答,我本身分析後的結論(不必定保證100%正確)

一、 爲何創建鏈接協議是三次握手,而關閉鏈接倒是四次握手呢?

這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它能夠把ACK和SYN(ACK起應答做用,而SYN起同步做用)放在一個報文裏來發送。但關閉鏈接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你全部的數據都所有發送給對方了,因此你能夠未必會立刻會關閉SOCKET,也即你可能還須要發送一些數據給對方以後,再發送FIN報文給對方來表示你贊成如今能夠關閉鏈接了,因此它這裏的ACK報文和FIN報文多數狀況下都是分開發送的。

二、 爲何TIME_WAIT狀態還須要等2MSL後才能返回到CLOSED狀態?

這是由於:雖然雙方都贊成關閉鏈接了,並且握手的4個報文也都協調和發送完畢,按理能夠直接回到CLOSED狀態(就比如從SYN_SEND狀態到ESTABLISH狀態那樣);可是由於咱們必需要假想網絡是不可靠的,你沒法保證你最後發送的ACK報文會必定被對方收到,所以對方處於LAST_ACK狀態下的SOCKET可能會由於超時未收到ACK報文,而重發FIN報文,因此這個TIME_WAIT狀態的做用就是用來重發可能丟失的ACK報文,並保證於此。

查看當前系統下全部鏈接狀態的數:

[root@vps ~]#netstat -n|awk '/^tcp/{++S[$NF]}END{for (key in S) print key,S[key]}'
TIME_WAIT 286
FIN_WAIT1 5
FIN_WAIT2 6
ESTABLISHED 269
SYN_RECV 5
CLOSING 1

如發現系統存在大量TIME_WAIT狀態的鏈接,經過調整內核參數解決:
編輯文件/etc/sysctl.conf,加入如下內容:

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

而後執行 /sbin/sysctl -p 讓參數生效。

net.ipv4.tcp_syncookies = 1 表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉;
net.ipv4.tcp_tw_reuse = 1 表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉;
net.ipv4.tcp_tw_recycle = 1 表示開啓TCP鏈接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。
net.ipv4.tcp_fin_timeout 修改系默認的 TIMEOUT 時間

其它參數說明:
net.ipv4.tcp_syncookies = 1 表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少許SYN攻擊,默認爲0,表示關閉;
net.ipv4.tcp_tw_reuse = 1 表示開啓重用。容許將TIME-WAIT sockets從新用於新的TCP鏈接,默認爲0,表示關閉;
net.ipv4.tcp_tw_recycle = 1 表示開啓TCP鏈接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。
net.ipv4.tcp_fin_timeout = 30 表示若是套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態的時間。
net.ipv4.tcp_keepalive_time = 1200 表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改成20分鐘。
net.ipv4.ip_local_port_range = 1024 65000 表示用於向外鏈接的端口範圍。缺省狀況下很小:32768到61000,改成1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192 表示SYN隊列的長度,默認爲1024,加大隊列長度爲8192,能夠容納更多等待鏈接的網絡鏈接數。
net.ipv4.tcp_max_tw_buckets = 5000 表示系統同時保持TIME_WAIT套接字的最大數量,若是超過這個數字,TIME_WAIT套接字將馬上被清除並打印警告信息。
默 認爲180000,改成5000。對於Apache、Nginx等服務器,上幾行的參數能夠很好地減小TIME_WAIT套接字數量,可是對於Squid,效果卻不大。此項參數能夠控制TIME_WAIT套接字的最大數量,避免Squid服務器被大量的TIME_WAIT套接字拖死。

注:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

設置這兩個參數: reuse是表示是否容許從新應用處於TIME-WAIT狀態的socket用於新的TCP鏈接; recyse是加速TIME-WAIT sockets回收

 http://blog.sina.com.cn/s/blog_8e5d24890102w9yi.html

 

用於統計當前各類狀態的鏈接的數量的命令

---------------------------

#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

 

返回結果以下:

LAST_ACK 14

SYN_RECV 348

ESTABLISHED 70

FIN_WAIT1 229

FIN_WAIT2 30

CLOSING 33

TIME_WAIT 18122

 

對上述結果的解釋:

CLOSED:無鏈接是活動的或正在進行

LISTEN:服務器在等待進入呼叫

SYN_RECV:一個鏈接請求已經到達,等待確認

SYN_SENT:應用已經開始,打開一個鏈接

ESTABLISHED:正常數據傳輸狀態

FIN_WAIT1:應用說它已經完成

FIN_WAIT2:另外一邊已贊成釋放

ITMED_WAIT:等待全部分組死掉

CLOSING:兩邊同時嘗試關閉

TIME_WAIT:另外一邊已初始化一個釋放

LAST_ACK:等待全部分組死掉

 

 

進一步論述這個問題:

===============================

 

 

--------------客戶端主動關閉鏈接-----------------------

注意一個問題,進入TIME_WAIT狀態的通常狀況下是客戶端。

大多數服務器端通常執行被動關閉,服務器不會進入TIME_WAIT狀態。

當在服務器端關閉某個服務再從新啓動時,服務器是會進入TIME_WAIT狀態的。

舉例:

1.客戶端鏈接服務器的80服務,這時客戶端會啓用一個本地的端口訪問服務器的80,訪問完成後關閉此鏈接,馬上再次訪問服務器的80,這時客戶端會啓用另外一個本地的端口,而不是剛纔使用的那個本地端口。緣由就是剛纔的那個鏈接還處於TIME_WAIT狀態。

2.客戶端鏈接服務器的80服務,這時服務器關閉80端口,當即再次重啓80端口的服務,這時可能不會成功啓動,緣由也是服務器的鏈接還處於TIME_WAIT狀態。

 

服務端提供服務時,通常監聽一個端口就夠了。例如Apach監聽80端口。

客戶端則是使用一個本地的空閒端口(大於1024),與服務端的Apache的80端口創建鏈接。

當通訊時使用短鏈接,並由客戶端主動關閉鏈接時,主動關閉鏈接的客戶端會產生TIME_WAIT狀態的鏈接,一個TIME_WAIT狀態的鏈接就佔用了一個本地端口。這樣在TIME_WAIT狀態結束以前,本地最多就能承受6萬個TIME_WAIT狀態的鏈接,就無故口可用了。

客戶端與服務端進行短鏈接的TCP通訊,若是在同一臺機器上進行壓力測試模擬上萬的客戶請求,而且循環與服務端進行短鏈接通訊,那麼這臺機器將產生4000個左右的TIME_WAIT socket,後續的短鏈接就會產生address already in use : connect的異常。

 

關閉的時候使用RST的方式,不進入 TIME_WAIT狀態,是否可行?

 

--------------服務端主動關閉鏈接------------------------------

服務端提供在服務時,通常監聽一個端口就夠了。例如Apach監聽80端口。

客戶端則是使用一個本地的空閒端口(大於1024),與服務端的Apache的80端口創建鏈接。

當通訊時使用短鏈接,並由服務端主動關閉鏈接時,主動關閉鏈接的服務端會產生TIME_WAIT狀態的鏈接。

因爲都鏈接到服務端80端口,服務端的TIME_WAIT狀態的鏈接會有不少個。

假如server一秒鐘處理1000個請求,那麼就會積壓240秒*1000=24萬個TIME_WAIT的記錄,服務有能力維護這24萬個記錄。

 

大多數服務器端通常執行被動關閉,服務器不會進入TIME_WAIT狀態。

服務端爲了解決這個TIME_WAIT問題,可選擇的方式有三種:

    Ø  保證由客戶端主動發起關閉(即作爲B端)

    Ø  關閉的時候使用RST的方式

    Ø  對處於TIME_WAIT狀態的TCP容許重用

 

通常Apache的配置是:

Timeout 30  

KeepAlive On   #表示服務器端不會主動關閉連接  

MaxKeepAliveRequests 100  

KeepAliveTimeout 180  

表示:Apache不會主動關閉連接,

兩種狀況下Apache會主動關閉鏈接:

一、Apache收到了http協議頭中有客戶端要求Apache關閉鏈接信息,如setRequestHeader("Connection", "close");  

二、鏈接保持時間達到了180秒的超時時間,將關閉。

 

若是配置以下:

KeepAlive Off   #表示服務器端會響應完數據後主動關閉連接  

 

 

--------------有代理時------------------------------

nginx代理使用了短連接的方式和後端交互,若是使用了nginx代理,那麼系統TIME_WAIT的數量會變得比較多,這是因爲nginx代理使用了短連接的方式和後端交互的緣由,使得nginx和後端的ESTABLISHED變得不多而TIME_WAIT不少。這不但發生在安裝nginx的代理服務器上,並且也會使後端的app服務器上有大量的TIME_WAIT。查閱TIME_WAIT資料,發現這個狀態不少也沒什麼大問題,但可能由於它佔用了系統過多的端口,致使後續的請求沒法獲取端口而形成障礙。

 

對於大型的服務,一臺server搞不定,須要一個LB(Load Balancer)把流量分配到若干後端服務器上,若是這個LB是以NAT方式工做的話,可能會帶來問題。假如全部從LB到後端Server的IP包的source address都是同樣的(LB的對內地址),那麼LB到後端Server的TCP鏈接會受限制,由於頻繁的TCP鏈接創建和關閉,會在server上留下TIME_WAIT狀態,並且這些狀態對應的remote address都是LB的,LB的source port撐死也就60000多個(2^16=65536,1~1023是保留端口,還有一些其餘端口缺省也不會用),每一個LB上的端口一旦進入Server的TIME_WAIT黑名單,就有240秒不能再用來創建和Server的鏈接,這樣LB和Server最多也就能支持300個左右的鏈接。若是沒有LB,不會有這個問題,由於這樣server看到的remote address是internet上廣闊無垠的集合,對每一個address,60000多個port實在是夠用了。

一開始我以爲用上LB會很大程度上限制TCP的鏈接數,可是實驗代表沒這回事,LB後面的一臺Windows Server 2003每秒處理請求數照樣達到了600個,難道TIME_WAIT狀態沒起做用?用Net Monitor和netstat觀察後發現,Server和LB的XXXX端口之間的鏈接進入TIME_WAIT狀態後,再來一個LB的XXXX端口的SYN包,Server照樣接收處理了,而是想像的那樣被drop掉了。翻書,從書堆裏面找出覆滿塵土的大學時代買的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中間提到一句,對於BSD-derived實現,只要SYN的sequence number比上一次關閉時的最大sequence number還要大,那麼TIME_WAIT狀態同樣接受這個SYN,難不成Windows也算BSD-derived?有了這點線索和關鍵字(BSD),找到這個post,在NT4.0的時候,仍是和BSD-derived不同的,不過Windows Server 2003已是NT5.2了,也許有點差異了。

作個試驗,用Socket API編一個Client端,每次都Bind到本地一個端口好比2345,重複的創建TCP鏈接往一個Server發送Keep-Alive=false的HTTP請求,Windows的實現讓sequence number不斷的增加,因此雖然Server對於Client的2345端口鏈接保持TIME_WAIT狀態,可是老是可以接受新的請求,不會拒絕。那若是SYN的Sequence Number變小會怎麼樣呢?一樣用Socket API,不過此次用Raw IP,發送一個小sequence number的SYN包過去,Net Monitor裏面看到,這個SYN被Server接收後如泥牛如海,一點反應沒有,被drop掉了。

按照書上的說法,BSD-derived和Windows Server 2003的作法有安全隱患,不過至少這樣至少不會出現TIME_WAIT阻止TCP請求的問題,固然,客戶端要配合,保證不一樣TCP鏈接的sequence number要上漲不要降低。

-------------------------------------------

Q: 我正在寫一個unix server程序,不是daemon,常常須要在命令行上重啓它,絕大多數時候工做正常,可是某些時候會報告"bind: address in use",因而重啓失敗。 

A: Andrew Gierth 

server程序老是應該在調用bind()以前設置SO_REUSEADDR套接字選項。至於 TIME_WAIT狀態,你沒法避免,那是TCP協議的一部分。

 

Q: 編寫 TCP/SOCK_STREAM 服務程序時,SO_REUSEADDR到底什麼意思? 

A: 這個套接字選項通知內核,若是端口忙,但TCP狀態位於 TIME_WAIT ,能夠重用 端口。若是端口忙,而TCP狀態位於其餘狀態,重用端口時依舊獲得一個錯誤信息, 指明"地址已經使用中"。若是你的服務程序中止後想當即重啓,而新套接字依舊 使用同一端口,此時 SO_REUSEADDR 選項很是有用。必須意識到,此時任何非期 望數據到達,均可能致使服務程序反應混亂,不過這只是一種可能,事實上很不 可能。 

 

一個套接字由相關五元組構成,協議、本地地址、本地端口、遠程地址、遠程端 口。SO_REUSEADDR 僅僅表示能夠重用本地本地地址、本地端口,整個相關五元組 仍是惟一肯定的。因此,重啓後的服務程序有可能收到非指望數據。必須慎重使用 SO_REUSEADDR 選項。 

相關文章
相關標籤/搜索