TCP time_wait詳解

TIME_WAIT狀態
TCP要保證在所有可能的情況下使得所有的數據都能夠正確被投遞。
當關閉一個 socket 連接時,主動關閉一端的 socket 將進入TIME_WAIT狀態,而被動關閉一方則轉入CLOSED狀態。
當一個socket關閉的時候,是通過兩端互發信息的四次握手過程完成的,當一端調用close()時,就說明本端沒有數據再要發送了。這好似看來在握手完成以後,socket就都應該處於關閉CLOSED狀態了。但這有兩個問題,
第一:我們沒有任何機制保證最後的一個ACK能夠正常送達
第二:網絡上仍然有可能有殘餘的數據包(wandering duplicates,或老的重複數據包),我們也必須能夠正常處理。
假設最後一個ACK丟失了,服務器會重發它發送的最後一個FIN,所以客戶端必須維持一個狀態信息,以便能夠重發ACK;如果不維持這種狀態,客戶端在接收到FIN後將會響應一個RST,服務器端接收到RST後會認爲這是一個錯誤。如果TCP協議能夠正常完成必要的操作而終止雙方的數據流傳輸,就必須完全正確的傳輸四次握手的四個節,不能有任何的丟失。這就是爲什麼socket在關閉後,仍然處於 TIME_WAIT狀態,因爲他要等待以便重發ACK。
如果目前連接的通信雙方都已經調用了close(),假定雙方都到達CLOSED狀態,而沒有TIME_WAIT狀態時,就會出現如下的情況。現在有一個新的連接被建立起來,使用的IP地址與端口與先前的完全相同,後建立的連接又稱作是原先連接的一個化身。還假定原先的連接中有數據報殘存於網絡之中,這樣新的連接收到的數據報中有可能是先前連接的數據報。爲了防止這一點,TCP不允許從處於TIME_WAIT狀態的socket建立一個連接。處於TIME_WAIT狀態的socket在等待兩倍的MSL時間以後(MSL:最大分段生存期,指明TCP報文在Internet上最長生存時間,每個具體的TCP實現 都必須選擇一個確定的MSL值。RFC 1122建議是2分鐘,但BSD傳統實現採用了30秒。TIME_WAIT 狀態最大保持時間是2 * MSL,也就是1-4分鐘。之所以是兩倍的MSL,是由於MSL是一個數據報文在網絡中單向發出到認定丟失的時間,一個數據報文有可能在發送途中或是其響應過程中成爲殘餘數據報文,確認一個數據報文及其響應的丟棄的需要兩倍的MSL),將會轉變爲CLOSED狀態。這就意味着,一個成功建立的連接,必然使得先前網絡中殘餘的數據報都丟了。
由於TIME_WAIT狀態所帶來的相關問題,我們可以通過設置SO_LINGER標誌來避免socket進入TIME_WAIT狀態,這可以通過發送RST而取代正常的TCP四次握手的終止方式。但這並不是一個很好的主意,TIME_WAIT對於我們來說往往是有利的。

TIME_WAIT狀態對HTTP影響
根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態,持續2*MSL(Max Segment Lifetime),默認爲240秒,在這個post中簡潔的介紹了爲什麼需要這個狀態。
值得一說的是,對於基於TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代操作系統都會用快速的查找算法來管理這些TIME_WAIT,所以對於新的TCP連接請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這麼多狀態要維護總是不好。
HTTP協議1.1版規定default行爲是Keep-Alive,也就是會重用TCP連接傳輸多個request/response,一個主要原因就是發現了這個問題。還有一個方法減緩TIME_WAIT壓力就是把系統的2*MSL時間減少,因爲240秒的時間實在是忒長了點,對於Windows,修改註冊表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,一般認爲不要少於60,不然可能會有麻煩。
對於大型的服務,一臺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要上漲不要下降。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章