1. 程式人生 > >TCP/IP詳解--TCP/IP連線各個狀態轉換分析

TCP/IP詳解--TCP/IP連線各個狀態轉換分析

一、Linux伺服器上11種網路連線狀態:



      圖:TCP的狀態機

通常情況下,一個正常的TCP連線,都會有三個階段:1、TCP三次握手; 2、資料傳送; 3、TCP四次揮手

注:以下說明最好能結合”圖:TCP的狀態機”來理解。

SYN: (同步序列編號,Synchronize Sequence Numbers)該標誌僅在三次握手建立TCP連線時有效。表示一個新的TCP連線請求。

ACK: (確認編號,Acknowledgement Number)是對TCP請求的確認標誌,同時提示對端系統已經成功接收所有資料。

FIN: (結束標誌,FINish)用來結束一個TCP回話.但對應埠仍處於開放狀態,準備接收後續資料。

1)、LISTEN:首先服務端需要開啟一個socket進行監聽,狀態為LISTEN. /* The socket is listening for incoming connections. 偵聽來自遠方TCP埠的連線請求 */
2)、SYN_SENT:客戶端通過應用程式呼叫connect進行active open.於是客戶端tcp傳送一個SYN以請求建立一個連線.之後狀態置為SYN_SENT. /*The socket is actively attempting to establish a connection. 在傳送連線請求後等待匹配的連線請求 */

3)、SYN_RECV:服務端應發出ACK確認客戶端的SYN,同時自己向客戶端傳送一個SYN. 之後狀態置為SYN_RECV /* A connection request has been received from the network. 在收到和傳送一個連線請求後等待對連線請求的確認 */

4)、ESTABLISHED: 代表一個開啟的連線,雙方可以進行或已經在資料互動了。/* The socket has an established connection. 代表一個開啟的連線,資料可以傳送給使用者 */

5)、FIN_WAIT1:主動關閉(active close)端應用程式呼叫close,於是其TCP發出FIN請求主動關閉連線,之後進入FIN_WAIT1狀態./* The socket is closed, and the connection is shutting down. 等待遠端TCP的連線中斷請求,或先前的連線中斷請求的確認 */

6)、CLOSE_WAIT:被動關閉(passive close)端TCP接到FIN後,就發出ACK以迴應FIN請求(它的接收也作為檔案結束符傳遞給上層應用程式),並進入CLOSE_WAIT. /* The remote end has shut down, waiting for the socket to close. 等待從本地使用者發來的連線中斷請求 */

7)、FIN_WAIT2:主動關閉端接到ACK後,就進入了FIN-WAIT-2 ./* Connection is closed, and the socket is waiting for a shutdown from the remote end. 從遠端TCP等待連線中斷請求 */

8)、LAST_ACK:被動關閉端一段時間後,接收到檔案結束符的應用程式將呼叫CLOSE關閉連線。這導致它的TCP也傳送一個 FIN,等待對方的ACK.就進入了LAST-ACK . /* The remote end has shut down, and the socket is closed. Waiting for acknowledgement. 等待原來發向遠端TCP的連線中斷請求的確認 */

9)、TIME_WAIT:在主動關閉端接收到FIN後,TCP就傳送ACK包,並進入TIME-WAIT狀態。/* The socket is waiting after close to handle packets still in the network.等待足夠的時間以確保遠端TCP接收到連線中斷請求的確認 */

10)、CLOSING: 比較少見./* Both sockets are shut down but we still don’t have all our data sent. 等待遠端TCP對連線中斷的確認 */

11)、CLOSED: 被動關閉端在接受到ACK包後,就進入了closed的狀態。連線結束./* The socket is not being used. 沒有任何連線狀態 */
TIME_WAIT狀態的形成只發生在主動關閉連線的一方。
  主動關閉方在接收到被動關閉方的FIN請求後,傳送成功給對方一個ACK後,將自己的狀態由FIN_WAIT2修改為TIME_WAIT,而必須再等2倍 的MSL(Maximum Segment Lifetime,MSL是一個數據報在internetwork中能存在的時間)時間之後雙方才能把狀態 都改為CLOSED以關閉連線。目前RHEL裡保持TIME_WAIT狀態的時間為60秒。當然上述很多TCP狀態在系統裡都有對應的解釋或設定,可見man tcp

二、關於長連線和短連線:
  通俗點講,短連線就是一次TCP請求得到結果後,連線馬上結束.而長連線並不馬上斷開,而一直保持著,直到長連線TIMEOUT(具體程式都有相關引數說明).長連線可以避免不斷的進行TCP三次握手和四次揮手.
長連線(keepalive)是需要靠雙方不斷的傳送探測包來維持的,keepalive期間服務端和客戶端的TCP連線狀態是ESTABLISHED.目前http 1.1版本里預設都是keepalive(1.0版本預設是不keepalive的),ie6/7/8和firefox都預設用的是http 1.1版本了(如何檢視當前瀏覽器用的是哪個版本,這裡不再贅述)。Apache,java

  一個應用至於到底是該使用短連線還是長連線,應該視具體情況而定。一般的應用應該使用長連線。

tcp 四次揮手

  TCP協議有一個優雅的關閉(graceful close)機制,以保證應用程式在關閉連線時不必擔心正在傳輸的資料會丟失。如第4.5節的壓縮示例程式所示,這個機制還設計為允許兩個方向的資料傳輸相互獨立地終止。關閉機制的工作流程是:應用程式通過呼叫連線套接字的close()方法或shutdownOutput()方法表明資料已經發送完畢。此刻,底層的TCP實現首先將留存在SendQ佇列中的資料傳輸出去(還要依賴於另一端RecvQ佇列的剩餘空間),然後向另一端傳送一個關閉TCP連線的握手訊息。該關閉握手訊息可以看作是流終止標誌:它告訴接收端TCP不會再有新的資料傳入RecvQ隊列了。(注意,關閉握手訊息本身並沒有傳遞給接收端應用程式,而是通過read()方法返回-1來指示其在位元組流中的位置。)正在關閉的TCP將等待其關閉握手訊息的確認資訊,該確認資訊表明在連線上傳輸的所有資料已經安全地傳輸到了RecvQ中。只要收到了確認訊息,該連線就變成"半關閉(Half closed)"狀態。直到連線的另一個方向上收到了對稱的握手訊息後,連線才完全關閉--也就是說,連線的兩端都表明它們再沒有資料要傳送了。

  TCP連線的關閉事件序列可能以兩種方式發生:一種方式是先由一個應用程式呼叫close()方法(或shutdownOutput()方法),並在另一端呼叫close()方法之前完成其關閉握手訊息;另一種方式是兩端同時呼叫close()方法,它們的關閉握手訊息在網路上交叉傳輸。圖6.10展示了以第一種方式關閉連線時,底層實現中的事件序列。關閉握手訊息已經發送,套接字資料結構的狀態也已經設定為"Closing"(專業術語稱為"FIN_WAIT_1"),然後close()呼叫返回。完成這些工作後,將禁止在該Socket上的任何讀寫操作(會丟擲異常)。當收到關閉握手確認訊息後,套接字資料結構的狀態則改變為"半關閉"(專業術語稱為"FIN_WAIT_2"),這種狀態將一直持續,直到接收到另一端的關閉握手訊息

  關閉TCP連線的最後微妙之處在於對Time-Wait狀態的需要。TCP規範要求在終止連線時,兩端的關閉握手都完成後,至少要有一個套接字在Time-Wait狀態保持一段時間。這個要求的提出是由於訊息在網路中傳輸時可能延遲。如果在連線兩端都完成了關閉握手後,它們都移除了其底層資料結構,而此時在同樣一對套接字地址之間又立即建立了新的連線,那麼前一個連線在網路上傳輸時延遲的訊息就可能在新連線建立後到達。由於其包含了相同的源地址和目的地址,舊訊息就會被錯誤地認為是屬於新連線的,其包含的資料就可能被錯誤地分配到應用程式中。

  雖然這種情形可能很少發生,TCP還是使用了包括Time-Wait狀態在內的多種機制對其進行防範。Time-Wait狀態用於保證每個TCP連線都在一段平靜時間內結束,這期間不會有資料傳送。平靜時間的長度應該等於分組報文在網路上存留的最長時間的兩倍。因此,當一個連線完全結束(即套接字資料結構離開Time-Wait狀態並被刪除),併為同樣一對地址上的新連線清理道路後,就不會再有舊例項傳送的訊息還存留在網路中。實際上,平靜時間的長度要依賴於具體實現,因為沒有機制能真正限制分組報文在網路上能夠延遲的時間。通常使用的時間範圍是4分鐘減到30秒,或更短。

  Time-Wait狀態最重要的作用是,只要底層套接字資料結構還存在,就不允許在相同的本地埠上關聯其他套接字。尤其是試圖使用該埠建立新的Socket例項時,將丟擲IOException異常。

TCP三次握手/四次揮手詳解

1、建立連線協議(三次握手)
(1)客戶端傳送一個帶SYN標誌的TCP報文到伺服器。這是三次握手過程中的報文1。
(2) 伺服器端迴應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標誌和SYN標誌。因此它表示對剛才客戶端SYN報文的迴應;同時又標誌SYN給客戶端,詢問客戶端是否準備好進行資料通訊。
(3) 客戶必須再次迴應服務段一個ACK報文,這是報文段3。

2、連線終止協議(四次揮手)
   由於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狀態。

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%正確):

1、 為什麼建立連線協議是三次握手,而關閉連線卻是四次握手呢?
這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文裡來發送。但關閉連線時,當收到對方的FIN報文通知時,它僅僅表示對方沒有資料傳送給你了;但未必你所有的資料都全部發送給對方了,所以你可以未必會馬上會關閉SOCKET,也即你可能還需要傳送一些資料給對方之後,再發送FIN報文給對方來表示你同意現在可以關閉連線了,所以它這裡的ACK報文和FIN報文多數情況下都是分開發送的。

2、 為什麼TIME_WAIT狀態還需要等2MSL後才能返回到CLOSED狀態?
這是因為:雖然雙方都同意關閉連線了,而且握手的4個報文也都協調和傳送完畢,按理可以直接回到CLOSED狀態(就好比從SYN_SEND狀態到ESTABLISH狀態那樣);但是因為我們必須要假想網路是不可靠的,你無法保證你最後傳送的ACK報文會一定被對方收到,因此對方處於LAST_ACK狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT狀態的作用就是用來重發可能丟失的ACK報文。