1. TCP連接的狀態
首先介紹一下TCP連接建立與關閉過程中的狀態。TCP連接過程是狀態的轉換,促使狀態發生轉換的因素包括用戶調用、特定數據包以及超時等,具體狀態如下所示:
- CLOSED:初始狀態,表示沒有任何連接。
- LISTEN:Server端的某個Socket正在監聽來自遠方的TCP端口的連接請求。
- SYN_SENT:發送連接請求后等待確認信息。當客戶端Socket進行Connect連接時,會首先發送SYN包,隨即進入SYN_SENT狀態,然后等待Server端發送三次握手中的第2個包。
- SYN_RECEIVED:收到一個連接請求后回送確認信息和對等的連接請求,然后等待確認信息。通常是建立TCP連接的三次握手過程中的一個中間狀態,表示Server端的Socket接收到來自Client的SYN包,並作出回應。
- ESTABLISHED:表示連接已經建立,可以進行數據傳輸。
- FIN_WAIT_1:主動關閉連接的一方等待對方返回ACK包。若Socket在ESTABLISHED狀態下主動關閉連接並向對方發送FIN包(表示己方不再有數據需要發送),則進入FIN_WAIT_1狀態,等待對方返回ACK包,此后還能讀取數據,但不能發送數據。在正常情況下,無論對方處於何種狀態,都應該馬上返回ACK包,所以FIN_WAIT_1狀態一般很難見到。
- FIN_WAIT_2:主動關閉連接的一方收到對方返回的ACK包后,等待對方發送FIN包。處於FIN_WAIT_1狀態下的Socket收到了對方返回的ACK包后,便進入FIN_WAIT_2狀態。由於FIN_WAIT_2狀態下的Socket需要等待對方發送的FIN包,所有常常可以看到。若在FIN_WAIT_1狀態下收到對方發送的同時帶有FIN和ACK的包時,則直接進入TIME_WAIT狀態,無須經過FIN_WAIT_2狀態。
- TIME_WAIT:主動關閉連接的一方收到對方發送的FIN包后返回ACK包(表示對方也不再有數據需要發送,此后不能再讀取或發送數據),然后等待足夠長的時間(2MSL)以確保對方接收到ACK包(考慮到丟失ACK包的可能和迷路重復數據包的影響),最后回到CLOSED狀態,釋放網絡資源。
- CLOSE_WAIT:表示被動關閉連接的一方在等待關閉連接。當收到對方發送的FIN包后(表示對方不再有數據需要發送),相應的返回ACK包,然后進入CLOSE_WAIT狀態。在該狀態下,若己方還有數據未發送,則可以繼續向對方進行發送,但不能再讀取數據,直到數據發送完畢。
- LAST_ACK:被動關閉連接的一方在CLOSE_WAIT狀態下完成數據的發送后便可向對方發送FIN包(表示己方不再有數據需要發送),然后等待對方返回ACK包。收到ACK包后便回到CLOSED狀態,釋放網絡資源。
- CLOSING:比較罕見的例外狀態。正常情況下,發送FIN包后應該先收到(或同時收到)對方的ACK包,再收到對方的FIN包,而CLOSING狀態表示發送FIN包后並沒有收到對方的ACK包,卻已收到了對方的FIN包。有兩種情況可能導致這種狀態:其一,如果雙方幾乎在同時關閉連接,那么就可能出現雙方同時發送FIN包的情況;其二,如果ACK包丟失而對方的FIN包很快發出,也會出現FIN先於ACK到達。
TCP連接的狀態轉換如下圖所示
2. TCP連接的關閉方式
建立TCP連接需要三次握手,而關閉連接則需要四次握手,並且分為主動關閉和被動關閉。這是由於TCP連接是全雙工的,我關了你的連接,並不等於你關了我的連接,因此雙方都必須單獨進行關閉。當一方完成它的數據發送任務后可以發送FIN包來終止這個方向的連接,表明自己不再有數據需要發送;收到FIN包的那一方雖然不能再讀取數據,但仍能發送數據。以Client主動關閉連接為例:
- Client向Server發送FIN包,表示Client主動關閉連接,然后進入FIN_WAIT_1狀態,等待Server返回ACK包。此后Client不能再向Server發送數據,但能讀取數據。
- Server收到FIN包后向Client發送ACK包,然后進入CLOSE_WAIT狀態,此后Server不能再讀取數據,但可以繼續向Client發送數據。Client收到Server返回的ACK包后進入FIN_WAIT_2狀態,等待Server發送FIN包。
- Server完成數據的發送后,將FIN包發送給Client,然后進入LAST_ACK狀態,等待Client返回ACK包,此后Server既不能讀取數據,也不能發送數據。
- Client收到FIN包后向Server發送ACK包,然后進入TIME_WAIT狀態,接着等待足夠長的時間(2MSL)以確保Server接收到ACK包,最后回到CLOSED狀態,釋放網絡資源。Server收到Client返回的ACK包后便回到CLOSED狀態,釋放網絡資源。
TCP連接的建立到關閉,需要經歷以下狀態遷移(假定Client發起連接,並主動關閉連接):
- Client
CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED
- Server
CLODES -> LISTEN -> SYN_RECEIVED -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
3. 對Server與Client的影響
在詳細了解TCP連接的狀態和關閉方式后,我們會發現TIME_WAIT狀態是一個坑爹的存在!主動關閉連接的一方在發送最后一個ACK包后,無論對方是否收到都會進入TIME_WAIT狀態,等待2MSL的時間,然后才能釋放網絡資源。MSL就是Maximum Segment Lifetime(數據包的最大生命周期),是一個數據包能在互聯網上生存的最長時間,若超過這個時間則該數據包將會消失在網絡中。操作系統通常會將2MSL設為4分鍾,最低不少於30秒,因而TIME_WAIT狀態一般維持在30秒至4分鍾。這個是TCP/IP協議必不可少的,是TCP/IP設計者設計的,也就是無法解決的。TIME_WAIT狀態的存在主要有兩個原因:
- 可靠地實現TCP全雙工連接的終止。在關TCP閉連接時,最后的ACK包是由主動關閉方發出的,如果這個ACK包丟失,則被動關閉方將重發FIN包,因此主動方必須維護狀態信息,以允許它重發這個ACK包。如果不維持這個狀態信息,那么主動方將回到CLOSED狀態,並對被動方重發的FIN包響應RST包,而被動關閉方將此包解釋成一個錯誤(在Java中會拋出connection reset的SocketException)。因而,要實現TCP全雙工連接的正常終止,必須能夠處理四次握手協議中任意一個包丟失的情況,主動關閉方必須維持狀態信息進入TIME_WAIT狀態。
- 確保迷路重復數據包在網絡中消失,防止上一次連接中的包迷路后重新出現,影響新連接。TCP數據包可能由於路由器異常而迷路,在迷路期間,數據包發送方可能因超時而重發這個包,迷路的數據包在路由器恢復后也會被送到目的地,這個迷路的數據包就稱為Lost Duplicate。在關閉一個TCP連接后,如果馬上使用相同的IP地址和端口建立新的TCP連接,那么有可能出現前一個連接的迷路重復數據包在前一個連接關閉后再次出現,影響新建立的連接。為了避免這一情況,TCP協議不允許使用處於TIME_WAIT狀態的連接的IP和端口啟動一個新連接,只有經過2MSL的時間,確保上一次連接中所有的迷路重復數據包都已消失在網絡中,才能安全地建立新連接。
對於Client而言,每個連接都需要占用一個端口,而系統允許的可用端口數不足65000個(這也是在TCP參數優化后才能達到)。因此,如果Client發起過多的連接並主動關閉(假設沒有重用端口或者連接多個Server),就會有大量的連接在關閉后處於TIME_WAIT狀態,等待2MSL的時間后才能釋放網絡資源(包括端口),於是Client會由於缺少可用端口而無法新建連接。
對Server而言(特別是處理高並發短連接的Server),Server端與Client建立的連接是使用同一個端口的,即監聽的端口,每個連接通過一個五元組區分,包括源IP地址、源端口、傳輸層協議號(協議類型)、目的IP地址、目的端口,因而在理論上,Server不受系統端口數的限制。但是,Server對每個端口上的連接數是有限制的,它要使用哈希表記錄端口上的每個連接,並受到文件描述符的最大打開數的限制。所以,如果Server主動關閉連接,同樣會有大量的連接在關閉后處於TIME_WAIT狀態,等待2MSL的時間后才能釋放網絡資源(包括哈希表上的連接記錄和文件描述符),於是Server會由於達到哈希表和文件描述符的限制而無法接受新連接,造成性能的急劇下滑,性能曲線會持續產生嚴重的波動。對於這種情況,有三種應對方式:
- 試圖讓Client主動關閉連接,由於每個Client的並發量都比較低,因而不會產生性能瓶頸。
- 優化Server的系統TCP參數,使其網絡資源的最大值、消耗速度和恢復速度達到平衡。
- 改寫TCP協議,重新實現底層代碼,不過該方式難度很大,而且系統的穩定性和安全性可能受到影響。
4. TCPWindowSize
TCPWindowSize的值表示TCP的窗口大小。TCP Receive Window(TCP數據接收緩沖)定義了發送端在沒有獲得接收端的確認信息的狀態下可以發送的最大字節數。此數值越大,返回的確認信息就越少,相應的在發送端和接收端之間的通信就越好。此數值較小時可以降低發送端在等待接收端返回確認信息時發生超時的可能性,但這將增加網絡流量,降低有效吞吐率。TCP在發送端和接收端之間動態調整一個最大段長度MSS(Maximum Segment Size)的整數倍。MSS在連接開始建立時確定,由於TCP Receive Window被調整為MSS的整數倍,在數據傳輸中完全長度的TCP數據段的比例增加,故而提高了網絡吞吐率。
缺省情況下,TCP將試圖根據MSS來優化窗口大小,起始值為16KB,最大值為64KB。TCPWindowSize的最大值通常為65535字節(64KB),以太網最大段長度為1460字節,低於64KB的1460的最大整數倍為62420字節,因而可以在注冊表中將TCPWindowSize設置為62420,作為高帶寬網絡中適用的性能優化值。具體操作如下:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為TCPWindowSize的REG_DWORD值,該值的范圍是從0到65535,將該值設置為62420。
5. TCP1323Opts
為了更高效地利用高帶寬網絡,可以使用比上述TCP窗口大得多的TCP窗口大小,此特性是Windows 2000和Windows Server 2003中的新特性,稱為TCP Window Scaling,它將以前的65535字節(64KB)的限制提高到了1073741824字節(1GB)。在帶寬與延遲的乘積值很高的連接上(例如衛星連接),可能需要將窗口的大小增加到64KB以上。使用TCP Window Scaling,系統可以允許確認信息間更大數據量的傳輸,增加了網絡吞吐量及性能。發送端和接收端往返通信所需的時間被稱為回環時間(RTT)。TCP Window Scaling僅在TCP連接的雙方都開啟時才真正有效。TCP有一個時間戳選項,通過更加頻繁地計算來提高RTT值的估測值,此選項特別有助於估測更長距離的廣域網上連接的RTT值,並更加精確地調整TCP重發超時時間。時間戳在TCP報頭提供了兩個區域,一個記錄開始重發的時間,另一個記錄接收到的時間。時間戳對於TCP Window Scaling,即確認信息收到前的大數據包傳送特別有用,激活時間戳僅僅在每個數據包的頭部增加12字節,對網絡流量的影響微乎其微。數據完整性與數據吞吐率最大化哪個更為重要是個需要評估的問題。在某些環境中,例如視頻流傳輸,需要更大的TCP窗口,這是最重要的,而數據完整性排在第二位。在這種環境中,TCP Window Scaling可以不打開時間戳。當發送端和接收端均激活TCP Window Scaling和時間戳時,此特性才有效。不過,若在發包時加入了時間戳,經過NAT之后,如果前面相同的端口被使用過,且時間戳大於這個連接發出的SYN中的時間戳,就會導致服務器忽略該SYN,表現為用戶無法正常完成TCP的3次握手。初始時生成小的TCP窗口,之后窗口大小將按照內部算法增大。具體操作如下:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為TCP1323Opts的REG_DWORD值,該值的具體含義為:0(缺省值)表示禁用TCP Window Scaling和時間戳;1表示只啟用TCP Window Scaling;2表示只啟用時間戳;3表示同時啟用TCP Window Scaling和時間戳。TCP1323Opts設置為激活TCP Window Scaling后,可以將上文中的注冊表項TCPWindowSize的值增大,最大能達到1GB,為了達到最佳性能,這里的值最好設置成MSS的倍數,推薦值為256960字節。
6. TCP 控制塊表
對於每個TCP連接,控制變量保存在一個稱為TCP控制塊(TCB)的內存塊中。TCB表的大小由注冊表項MaxHashTableSize控制。在活動連接很多的系統中,設定一個較大的表可以降低系統定位TCB表的時間。在TCB表上分區可以降低對表的訪問的爭奪。增加分區的數量,TCP的性能會得到優化,特別是在多處理器的系統上。注冊表項NumTcbTablePartitions控制分區的數量,默認是處理器個數的平方。TCB通常預置在內存中,以防止TCP反復連接和斷開時,TCB反復重新定位浪費時間,這種緩沖的方式促進了內存管理,但同時也限制了同一時刻允許的TCP連接數量。注冊表項MaxFreeTcbs決定了處於空閑等待狀態的TCB重新可用之前的連接數量,在NT架構中常設置成高於默認值,以確保有足夠的預置的TCB。從Windows 2000開始添加了一個新特性,降低超出預置TCB運行的可能性。如果處於等待狀態的連接多於MaxFreeTWTcbs中的設置,所有等待時間超過60秒的連接將被強制關閉,以后再次啟用。此特性合並到Windows 2000 Server和Windows Server 2003后,MaxFreeTcbs將不再用於優化性能。具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為MaxHashTableSize的REG_DWORD值,該值的范圍是從1到65536,並且必須為2的N次方,缺省值為512,建議設為8192。然后在Parameters子鍵下創建或修改名為NumTcbTablePartitions的REG_DWORD值,該值的范圍是從1到65536,並且必須為2的N次方,缺省值為處理器個數的平方,建議設為處理器核心數的4倍。
7. TcpTimedWaitDelay
TcpTimedWaitDelay的值表示系統釋放已關閉的TCP連接並復用其資源之前,必須等待的時間。這段時間間隔就是以前的Blog中提到的TIME_WAIT狀態(2MSL,數據包最長生命周期的兩倍狀態)。如果系統顯示大量連接處於TIME_WAIT狀態,則會導致並發量與吞吐量的嚴重下降,通過減小該項的值,系統可以更快地釋放已關閉的連接,從而為新連接提供更多的資源,特別是對於高並發短連接的Server具有積極的意義。
該項的缺省值是240,即等待4分鍾后釋放資源;系統支持的最小值為30,即等待時間為30秒。具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為TcpTimedWaitDelay的REG_DWORD值,該值的范圍是從0到300,建議將該值設置為30。
8. MaxUserPort
MaxUserPort的值表示當應用程序向系統請求可用的端口時,TCP/IP可分配的最大端口號。如果系統顯示建立連接時出現異常,那么有可能是由於匿名(臨時)端口數不夠導致的,特別是當系統打開大量端口來與Web service、數據庫或其他遠程資源建立連接時。
該項的缺省值是十進制的5000,這也是系統允許的最小值。Windows默認為匿名(臨時)端口保留的端口號范圍是從1024到5000。為了獲得更高的並發量,建議將該值至少設為32768以上,甚至設為理論最大值65534,特別是對於模擬高並發測試環境的Client具有積極的意義。具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為MaxUserPort的REG_DWORD值,該值的范圍是從5000到65534,缺省值為5000,建議將該值設置為65534。
9. 動態儲備
動態儲備的值使系統能自動調整其配置,以接受大量突發的連接請求。如果同時接收到大量連接請求,超出了系統的處理能力,那么動態儲備就會自動增大系統支持的暫掛連接的數量(即Client已請求而Server尚未處理的等待連接數,TCP連接的總數包括已連接數與等待連接數),從而可減少連接失敗的數量。系統的處理能力和支持的暫掛連接的數量不足時,Client的連接請求將直接被拒絕。
缺省情況下,Windows 不啟用動態儲備,可以通過以下操作進行開啟和設置:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters注冊表子鍵,在Parameters子鍵下創建或修改下列名稱的REG_DWORD值。
- EnableDynamicBacklog,值為1,表示開啟動態儲備。
- MinimumDynamicBacklog,值為128,表示支持的最小暫掛連接的數量為128。
- MaximumDynamicBacklog,值為2048,表示支持的最大暫掛連接的數量為2048。對於高並發短連接的Server,建議最大值設為1024及以上。
- DynamicBacklogGrowthDelta,值為128,表示支持的暫掛連接的數量的增量為128,即數量不足時自增長128,直到達到設定的最大值,如2048。
10. KeepAliveTime
KeepAliveTime的值控制系統嘗試驗證空閑連接是否仍然完好的頻率。如果該連接在一段時間內沒有活動,那么系統會發送保持連接的信號,如果網絡正常並且接收方是活動的,它就會響應。如果需要對丟失接收方的情況敏感,也就是說需要更快地發現是否丟失了接收方,請考慮減小該值。而如果長期不活動的空閑連接的出現次數較多,但丟失接收方的情況出現較少,那么可能需要增大該值以減少開銷。
缺省情況下,如果空閑連接在7200000毫秒(2小時)內沒有活動,系統就會發送保持連接的消息。 通常建議把該值設為1800000毫秒,從而丟失的連接會在30分鍾內被檢測到。具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為KeepAliveTime的REG_DWORD值,為該值設置適當的毫秒數。
11. KeepAliveInterval
KeepAliveInterval的值表示未收到另一方對“保持連接”信號的響應時,系統重復發送“保持連接”信號的頻率。在無任何響應的情況下,連續發送“保持連接”信號的次數超過TcpMaxDataRetransmissions(下文將介紹)的值時,將放棄該連接。如果網絡環境較差,允許較長的響應時間,則考慮增大該值以減少開銷;如果需要盡快驗證是否已丟失接收方,則考慮減小該值或TcpMaxDataRetransmissions值。
缺省情況下,在未收到響應而重新發送“保持連接”的信號之前,系統會等待1000毫秒(1秒),可以根據具體需求修改,具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為KeepAliveInterval的REG_DWORD值,為該值設置適當的毫秒數。
12. TcpMaxDataRetransmissions
TcpMaxDataRetransmissions的值表示TCP數據重發,系統在現有連接上對無應答的數據段進行重發的次數。如果網絡環境很差,可能需要提高該值以保持有效的通信,確保接收方收到數據;如果網絡環境很好,或者通常是由於丟失接收方而導致數據的丟失,那么可以減小該值以減少驗證接收方是否丟失所花費的時間和開銷。
缺省情況下,系統會重新發送未返回應答的數據段5次,可以根據具體需求修改,具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為TcpMaxDataRetransmissions的REG_DWORD值,該值的范圍是從0到4294967295,缺省值為5,根據實際情況進行設置。
13. TcpMaxConnectRetransmisstions
TcpMaxConnectRetransmisstions的值表示TCP連接重發,TCP退出前重發非確認連接請求(SYN)的次數。對於每次嘗試,重發超時是成功重發的兩倍。在Windows Server 2003中默認超時次數是2,默認超時時間為3秒(在注冊表項TCPInitialRTT中)。速度較慢的WAN連接中超時時間可相應增加,不同環境中可能會有不同的最優化設置,需要在實際環境中測試確定。超時時間不要設置太大否則將不會發生網絡連接超時時間。具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters注冊表子鍵,在Parameters子鍵下創建或修改名為TcpMaxConnectRetransmisstions的REG_DWORD值,該值的范圍是從0到255,缺省值為2,根據實際情況進行設置。然后在Parameters子鍵下創建或修改名為TCPInitialRTT的REG_DWORD值,同樣根據實際情況進行設置。
14. TcpAckFrequency
TcpAckFrequency的值表示系統發送應答消息的頻率。如果值為2,那么系統將在接收到2個分段之后發送應答,或是在接收到1個分段但在200毫秒內沒有接收到任何其他分段的情況下發送應答;如果值為3,那么系統將在接收到3個分段之后發送應答,或是在接收到1個或2個分段但在200毫秒內沒有接收到任何其他分段的情況下發送應答,以此類推。如果要通過消除應答延遲來縮短響應時間,那么建議將該值設為1。在此情況下,系統會立即發送對每個分段的應答;如果連接主要用於傳輸大量數據,而200毫秒的延遲並不重要,那么可以減小該值以降低應答的開銷。
缺省情況下,系統將該值設為2,即每隔一個分段應答一次。該值的有效范圍是0到255,其中0表示使用缺省值2,可以根據具體需求修改,具體操作:
瀏覽至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TCPIP\Parameters\Interfaces\xx(xx由網絡適配器決定)注冊表子鍵,在xx子鍵下創建或修改名為TcpAckFrequency的REG_DWORD值,該值的范圍是從1到13,缺省值為2,根據希望每發送幾個分段返回一個應答而設置該值,建議百兆網絡設為5,千兆網絡設為13。