1. HTTP協議與TCP/IP協議的關系
HTTP的長連接和短連接本質上是TCP長連接和短連接。HTTP屬於應用層協議,在傳輸層使用TCP協議,在網絡層使用IP協議。IP協議主要解決網絡路由和尋址問題,TCP協議主要解決如何在IP層之上可靠的傳遞數據包,使在網絡上的另一端收到發端發出的所有包,並且順序與發出順序一致。TCP有可靠,面向連接的特點。
2. 如何理解HTTP協議是無狀態的
HTTP協議是無狀態的,指的是協議對於事務處理沒有記憶能力,服務器不知道客戶端是什么狀態。也就是說,打開一個服務器上的網頁和你之前打開這個服務器上的網頁之間沒有任何聯系。HTTP是一個無狀態的面向連接的協議,無狀態不代表HTTP不能保持TCP連接,更不能代表HTTP使用的是UDP協議(無連接)。
3. 什么是長連接、短連接?
在HTTP/1.0中,默認使用的是短連接。也就是說,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。如果客戶端瀏覽器訪問的某個HTML或其他類型的 Web頁中包含有其他的Web資源,如JavaScript文件、圖像文件、CSS文件等;當瀏覽器每遇到這樣一個Web資源,就會建立一個HTTP會話。
但從 HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭有加入這行代碼:
Connection:keep-alive
|
在使用長連接的情況下,當一個網頁打開完成后,客戶端和服務器之間用於傳輸HTTP數據的 TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的連接。Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件(如Apache)中設定這個時間。實現長連接要客戶端和服務端都支持長連接。
HTTP協議的長連接和短連接,實質上是TCP協議的長連接和短連接。
3.1 TCP連接
當網絡通信時采用TCP協議時,在真正的讀寫操作之前,server與client之間必須建立一個連接,當讀寫操作完成后,雙方不再需要這個連接 時它們可以釋放這個連接,連接的建立是需要三次握手的,而釋放則需要4次握手,所以說每個連接的建立都是需要資源消耗和時間消耗的
經典的三次握手示意圖:

經典的四次握手關閉圖:

3.2 TCP短連接
我們模擬一下TCP短連接的情況,client向server發起連接請求,server接到請求,然后雙方建立連接。client向server 發送消息,server回應client,然后一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起 close操作。為什么呢,一般的server不會回復完client后立即關閉連接的,當然不排除有特殊的情況。從上面的描述看,短連接一般只會在 client/server間傳遞一次讀寫操作
短連接的優點是:管理起來比較簡單,存在的連接都是有用的連接,不需要額外的控制手段
3.3 TCP長連接
接下來我們再模擬一下長連接的情況,client向server發起連接,server接受client連接,雙方建立連接。Client與server完成一次讀寫之后,它們之間的連接並不會主動關閉,后續的讀寫操作會繼續使用這個連接。
首先說一下TCP/IP詳解上講到的TCP保活功能,保活功能主要為服務器應用提供,服務器應用希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。如果客戶已經消失,使得服務器上保留一個半開放的連接,而服務器又在等待來自客戶端的數據,則服務器將應遠等待客戶端的數據,保活功能就是試圖在服務 器端檢測到這種半開放的連接。
如果一個給定的連接在兩小時內沒有任何的動作,則服務器就向客戶發一個探測報文段,客戶主機必須處於以下4個狀態之一:
- 客戶主機依然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常的,服務器在兩小時后將保活定時器復位。
- 客戶主機已經崩潰,並且關閉或者正在重新啟動。在任何一種情況下,客戶的TCP都沒有響應。服務端將不能收到對探測的響應,並在75秒后超時。服務器總共發送10個這樣的探測 ,每個間隔75秒。如果服務器沒有收到一個響應,它就認為客戶主機已經關閉並終止連接。
- 客戶主機崩潰並已經重新啟動。服務器將收到一個對其保活探測的響應,這個響應是一個復位,使得服務器終止這個連接。
- 客戶機正常運行,但是服務器不可達,這種情況與2類似,TCP能發現的就是沒有收到探查的響應。
3.4 長連接短連接操作過程
短連接的操作步驟是:
建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接
長連接的操作步驟是:
建立連接——數據傳輸...(保持連接)...數據傳輸——關閉連接
4. 長連接和短連接的優點和缺點
由上可以看出,長連接可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。對於頻繁請求資源的客戶來說,較適用長連接。不過這里存在一個問題,存活功能的探測周期太長,還有就是它只是探測TCP連接的存活,屬於比較斯文的做法,遇到惡意的連接時,保活功能就不夠使了。在長連接的應用場景下,client端一般不會主動關閉它們之間的連接,Client與server之間的連接如果一直不關閉的話,會存在一個問題,隨着客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些策略,如關閉一些長時間沒有讀寫事件發生的連接,這樣可 以避免一些惡意連接導致server端服務受損;如果條件再允許就可以以客戶端機器為顆粒度,限制每個客戶端的最大長連接數,這樣可以完全避免某個蛋疼的客戶端連累后端服務。
短連接對於服務器來說管理較為簡單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關閉操作上浪費時間和帶寬。
長連接和短連接的產生在於client和server采取的關閉策略,具體的應用場景采用具體的策略,沒有十全十美的選擇,只有合適的選擇。
5. 什么時候用長連接,短連接? 長連接多用於操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。 而像WEB網站的http服務一般都用短鏈接,因為長連接對於服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。所以並發量大,但每個用戶無需頻繁操作情況下需用短連好。
http://www.cnblogs.com/0201zcr/p/4694945.html
補充:
在TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接,如圖1所示。
(1) 第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。SYN: 同步序列編號(Synchronize Sequence Numbers)
(2) 第二次握手:服務器B收到SYN包,必須確認客戶A的SYN(ACK=j+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。
(3) 第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包ACK(ACK=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據。

圖1 TCP三次握手建立連接
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送(報文段4)。
(2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。
(3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A(報文段6)。
(4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)。
TCP采用四次揮手關閉連接如圖2所示。
圖2 TCP四次揮手關閉連接
1.為什么建立連接協議是三次握手,而關閉連接卻是四次握手呢?
這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的連接請求后,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可能未必會馬上會關閉SOCKET,也即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這里的ACK報文和FIN報文多數情況下都是分開發送的。
2.為什么TIME_WAIT狀態還需要等2MSL后才能返回到CLOSED狀態?
這個問題可以參考《unix 網絡編程》(第三版,2.7 TIME_WAIT狀態)。
TIME_WAIT狀態由兩個存在的理由。
(1)可靠的實現TCP全雙工鏈接的終止。
這是因為雖然雙方都同意關閉連接了,而且握手的4個報文也都協調和發送完畢,按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最后發送的ACK報文會一定被對方收到,因此對方處於LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。
(2)允許老的重復的分節在網絡中消逝。
假設在12.106.32.254的1500端口和206.168.1.112.219的21端口之間有一個TCP連接。我們關閉這個鏈接,過一段時間后在相同的IP地址和端口建立另一個連接。后一個鏈接成為前一個的化身。因為它們的IP地址和端口號都相同。TCP必須防止來自某一個連接的老的重復分組在連接已經終止后再現,從而被誤解成屬於同一鏈接的某一個某一個新的化身。為做到這一點,TCP將不給處於TIME_WAIT狀態的鏈接發起新的化身。既然TIME_WAIT狀態的持續時間是MSL的2倍,這就足以讓某個方向上的分組最多存活msl秒即被丟棄,另一個方向上的應答最多存活msl秒也被丟棄。通過實施這個規則,我們就能保證每成功建立一個TCP連接時。來自該鏈接先前化身的重復分組都已經在網絡中消逝了。
3. 為什么不能用兩次握手進行連接?
我們知道,3次握手完成兩個重要的功能,既要雙方做好發送數據的准備工作(雙方都知道彼此已准備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被發送和確認。 現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。作為例子,考慮計算機S和C之間的通信,假定C給S發送一個連接請求分組,S收到了這個分組,並發 送了確認應答分組。按照兩次握手的協定,S認為連接已經成功地建立了,可以開始發送數據分組。可是,C在S的應答分組在傳輸中被丟失的情況下,將不知道S 是否已准備好,不知道S建立什么樣的序列號,C甚至懷疑S是否收到自己的連接請求分組。在這種情況下,C認為連接還未建立成功,將忽略S發來的任何數據分 組,只等待連接確認應答分組。而S在發出的分組超時后,重復發送同樣的分組。這樣就形成了死鎖。
補充:
a. 默認情況下(不改變socket選項),當你調用close( or closesocket,以下說close不再重復)時,如果發送緩沖中還有數據,TCP會繼續把數據發送完。
b. 發送了FIN只是表示這端不能繼續發送數據(應用層不能再調用send發送),但是還可以接收數據。
c. 應用層如何知道對端關閉?通常,在最簡單的阻塞模型中,當你調用recv時,如果返回0,則表示對端關閉。在這個時候通常的做法就是也調用close,那么TCP層就發送FIN,繼續完成四次握手。如果你不調用close,那么對端就會處於FIN_WAIT_2狀態,而本端則會處於CLOSE_WAIT狀態。這個可以寫代碼試試。
d. 在很多時候,TCP連接的斷開都會由TCP層自動進行,例如你CTRL+C終止你的程序,TCP連接依然會正常關閉,你可以寫代碼試試。
插曲:
特別的TIME_WAIT狀態:
從以上TCP連接關閉的狀態轉換圖可以看出,主動關閉的一方在發送完對對方FIN報文的確認(ACK)報文后,會進入TIME_WAIT狀態。TIME_WAIT狀態也稱為2MSL狀態。
什么是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:“它(MSL)是任何報文段被丟棄前在網絡內的最長時間。”那么,2MSL也就是這個時間的2倍。其實我覺得沒必要把這個MSL的確切含義搞明白,你所需要明白的是,當TCP連接完成四個報文段的交換時,主動關閉的一方將繼續等待一定時間(2-4分鍾),即使兩端的應用程序結束。你可以寫代碼試試,然后用setstat查看下。
為什么需要2MSL?根據《TCP/IP詳解》和《The TCP/IP Guide》中的說法,有兩個原因:
其一,保證發送的ACK會成功發送到對方,如何保證?我覺得可能是通過超時計時器發送。這個就很難用代碼演示了。
其二,報文可能會被混淆,意思是說,其他時候的連接可能會被當作本次的連接。直接引用《The TCP/IP Guide》的說法:The second is to provide a“buffering period” between the end of this connection and any subsequent ones. If not for this period, it is possible that packets from different connections could be mixed, creating confusion.
TIME_WAIT狀態所帶來的影響:
當某個連接的一端處於TIME_WAIT狀態時,該連接將不能再被使用。事實上,對於我們比較有現實意義的是,這個端口將不能再被使用。某個端口處於TIME_WAIT狀態(其實應該是這個連接)時,這意味着這個TCP連接並沒有斷開(完全斷開),那么,如果你bind這個端口,就會失敗。對於服務器而言,如果服務器突然crash掉了,那么它將無法再2MSL內重新啟動,因為bind會失敗。解決這個問題的一個方法就是設置socket的SO_REUSEADDR選項。這個選項意味着你可以重用一個地址。
對於TIME_WAIT的插曲:
當建立一個TCP連接時,服務器端會繼續用原有端口監聽,同時用這個端口與客戶端通信。而客戶端默認情況下會使用一個隨機端口與服務器端的監聽端口通信。有時候,為了服務器端的安全性,我們需要對客戶端進行驗證,即限定某個IP某個特定端口的客戶端。客戶端可以使用bind來使用特定的端口。對於服務器端,當設置了SO_REUSEADDR選項時,它可以在2MSL內啟動並listen成功。
但是對於客戶端,當使用bind並設置SO_REUSEADDR時,如果在2MSL內啟動,雖然bind會成功,但是在windows平台上connect會失敗。而在linux上則不存在這個問題。(我的實驗平台:winxp, ubuntu7.10)
要解決windows平台的這個問題,可以設置SO_LINGER選項。SO_LINGER選項決定調用close時TCP的行為。SO_LINGER涉及到linger結構體,如果設置結構體中l_onoff為非0,l_linger為0,那么調用close時TCP連接會立刻斷開,TCP不會將發送緩沖中未發送的數據發送,而是立即發送一個RST報文給對方,這個時候TCP連接就不會進入TIME_WAIT狀態。如你所見,這樣做雖然解決了問題,但是並不安全。通過以上方式設置SO_LINGER狀態,等同於設置SO_DONTLINGER狀態。
斷開連接時的意外:
這個算不上斷開連接時的意外,當TCP連接發生一些物理上的意外情況時,例如網線斷開,linux上的TCP實現會依然認為該連接有效,而windows則會在一定時間后返回錯誤信息。這似乎可以通過設置SO_KEEPALIVE選項來解決,不過不知道這個選項是否對於所有平台都有效。
轉自:http://blog.chinaunix.net/uid-25002135-id-3314682.html
http://www.cnblogs.com/luffya/p/3472493.html
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,是由於MSL是一個數據報在網絡中單向發出到認定丟失的時間,一個數據報有可能在發送圖中或是其響應過程中成為殘余數據報,確認一個數據報及其響應的丟棄的需要兩倍的MSL),將會轉變為CLOSED狀態。這就意味着,一個成功建立的連接,必然使得先前網絡中殘余的數據報都丟失了。 由於TIME_WAIT狀態所帶來的相關問題,我們可以通過設置SO_LINGER標志來避免socket進入TIME_WAIT狀態,這可以通過發送RST而取代正常的TCP四次握手的終止方式。但這並不是一個很好的主意,TIME_WAIT對於我們來說往往是有利的。
---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
Q: 編寫 TCP/SOCK_STREAM 服務程序時,SO_REUSEADDR到底什么意思?
A: 這個套接字選項通知內核,如果端口忙,但TCP狀態位於 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP狀態位於其他狀態,重用端口時依舊得到一個錯誤信息, 指明"地址已經使用中"。如果你的服務程序停止后想立即重啟,而新套接字依舊 使用同一端口,此時 SO_REUSEADDR 選項非常有用。必須意識到,此時任何非期 望數據到達,都可能導致服務程序反應混亂,不過這只是一種可能,事實上很不 可能。
一個套接字由相關五元組構成,協議、本地地址、本地端口、遠程地址、遠程端 口。SO_REUSEADDR 僅僅表示可以重用本地本地地址、本地端口,整個相關五元組 還是唯一確定的。所以,重啟后的服務程序有可能收到非期望數據。必須慎重使 用 SO_REUSEADDR 選項。
Q: 在客戶機/服務器編程中(TCP/SOCK_STREAM),如何理解TCP自動機 TIME_WAIT 狀 態?
A: W. Richard Stevens <1999年逝世,享年49歲>
下面我來解釋一下 TIME_WAIT 狀態,這些在<> 中2.6節解釋很清楚了。
MSL(最大分段生存期)指明TCP報文在Internet上最長生存時間,每個具體的TCP實現 都必須選擇一個確定的MSL值。RFC 1122建議是2分鍾,但BSD傳統實現采用了30秒。
TIME_WAIT 狀態最大保持時間是2 * MSL,也就是1-4分鍾。
IP頭部有一個TTL,最大值255。盡管TTL的單位不是秒(根本和時間無關),我們仍需 假設,TTL為255的TCP報文在Internet上生存時間不能超過MSL。
TCP報文在傳送過程中可能因為路由故障被迫緩沖延遲、選擇非最優路徑等等,結果 發送方TCP機制開始超時重傳。前一個TCP報文可以稱為"漫游TCP重復報文",后一個 TCP報文可以稱為"超時重傳TCP重復報文",作為面向連接的可靠協議,TCP實現必須 正確處理這種重復報文,因為二者可能最終都到達。
一個通常的TCP連接終止可以用圖描述如下:
client server FIN M close(主動關閉) -----------------> (被動關閉)
ACK M+1 <---------------------------------
FIN N <------------------------------- CLOSED
ACK N+1 -------------------------------> TIME_WAIT
為什么需要 TIME_WAIT 狀態?
假設最終的ACK丟失,server將重發FIN,client必須維護TCP狀態信息以便可以重發 最終的ACK,否則會發送RST,結果server認為發生錯誤。TCP實現必須可靠地終止連 接的兩個方向(全雙工關閉),client必須進入 TIME_WAIT 狀態,因為client可能面 臨重發最終ACK的情形。
此外,考慮一種情況,TCP實現可能面臨先后兩個同樣的相關五元組。如果前一個連 接處在 TIME_WAIT 狀態,而允許另一個擁有相同相關五元組的連接出現,可能處理 TCP報文時,兩個連接互相干擾。使用 SO_REUSEADDR 選項就需要考慮這種情況。
為什么 TIME_WAIT 狀態需要保持 2MSL 這么長的時間?
如果 TIME_WAIT 狀態保持時間不足夠長(比如小於2MSL),第一個連接就正常終止了。 第二個擁有相同相關五元組的連接出現,而第一個連接的重復報文到達,干擾了第二 個連接。TCP實現必須防止某個連接的重復報文在連接終止后出現,所以讓TIME_WAIT 狀態保持時間足夠長(2MSL),連接相應方向上的TCP報文要么完全響應完畢,要么被 丟棄。建立第二個連接的時候,不會混淆。
A: 小四
在Solaris 7下有內核參數對應 TIME_WAIT 狀態保持時間
# ndd -get /dev/tcp tcp_time_wait_interval 240000 # ndd -set /dev/tcp tcp_time_wait_interval 1000
缺省設置是240000ms,也就是4分鍾。如果用ndd修改這個值,最小只能設置到1000ms, 也就是1秒。顯然內核做了限制,需要Kernel Hacking。
# echo "tcp_param_arr/W 0t0" | adb -kw /dev/ksyms /dev/mem physmem 3b72 tcp_param_arr: 0x3e8 = 0x0 # ndd -set /dev/tcp tcp_time_wait_interval 0
我不知道這樣做有什么災難性后果,參看<>的聲明。
Q: TIME_WAIT 狀態保持時間為0會有什么災難性后果?在普遍的現實應用中,好象也 就是服務器不穩定點,不見得有什么災難性后果吧?
Linux 內核源碼 /usr/src/linux/include/net/tcp.h 中
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to successfully * close the socket, about 60 seconds */
最好不要改為0,改成1。端口分配是從上一次分配的端口號+1開始分配的,所以一般 不會有什么問題。端口分配算法在tcp_ipv4.c中tcp_v4_get_port中。
---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- 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要上漲不要下降。
文章鏈接: socket中的TIME_WAIT狀態 http://www.cnblogs.com/alon/archive/2009/04/01/1427625.html TCP TIME_WAIT狀態QA http://www.cnblogs.com/jason-jiang/archive/2006/11/03/549337.html TIME_WAIT狀態對HTTP影響 http://morganchengmo.spaces.live.com/blog/cns!9950CE918939932E!1721.entry
轉自:http://blog.chinaunix.net/uid-20384806-id-1954363.html
http://www.cnblogs.com/luffya/p/3472563.html
syn數據結構
http://www.cnblogs.com/hancm/p/3739301.html
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連接,但另外一方告訴對方,我暫時還有點數據需要傳送給你(ACK信息),稍后再關閉連接。(主動方)
TIME_WAIT: 表示收到了對方的FIN報文,並發送出了ACK報文,就等2MSL后即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標志和ACK標志的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。(主動方)
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可用狀態了。(被動方)
CLOSED: 表示連接中斷。
#.為什么需要4次揮手
原因是TCP是全雙工模式,接收到FIN時意味將沒有數據再發來,但是還是可以繼續發送數據。
http://www.cnblogs.com/codingthings/p/4427823.html
http://www.cnblogs.com/aguncn/archive/2012/12/21/2827520.html

http://www.cnblogs.com/sybtjp/archive/2012/05/17/2506156.html
