1. 程式人生 > >TCP狀態遷移圖淺析

TCP狀態遷移圖淺析

一.TCP簡介
     TCP提供一種面向連線的,可靠的位元組流服務。面對連線意味著兩個使用TCP的應用,在彼此交換資料之前必須先建立一個連線。TCP通過以下方式提供可靠性:
     1. 應用資料被分割成TCP認為最適合傳送的資料塊,由TCP傳遞給IP的資訊單位成為報文段。 
     2. 當TCP發出一個段後,它就啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。
     3. 當TCP收到TCP連線另一端的資料,它將傳送一個確認。這個確認不是立即傳送,而是延遲傳送的。
     4. TCP將保持對它首部和資料的校驗和,這是一個端到端的校驗和,目的是檢測資料在傳輸在傳送過程中的任何變化。如果收到段得檢驗和有錯誤,TCP將丟棄這個報文段和不確認收到報文段。
     5. TCP將對收到的資料進行重新排序,將收到的資料以正確的方式交給應用層。
     6. TCP的接收端必須丟棄重複的資料
     7. TCP還能提供流量控制,TCP連線的每一方都有固定大小的緩衝空間。TCP的接收端只允許另一端傳送接收端緩衝區所能接納的資料。這將防止較快主機致使較慢主機的緩衝區溢位。

二. TCP狀態遷移路線圖

下面的文章按照client/server兩條路線講述TCP狀態遷移路線圖。


 

還有一張比較經典的圖

tcpclose 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)。

經典的四次握手關閉圖:


3、各種狀態說明

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可用狀態了。

4. 連線建立

   1) Client
當Client端呼叫socket函式呼叫時,相當於Client端產生了一個處於Closed狀態的套接字。
Client端又呼叫
connect函式呼叫,系統為Client隨機分配一個埠,連同傳入connect中的引數(Server的IP和埠),這就形成了一個連線四元組,connect呼叫讓Client端的socket處於SYN_SENT狀態
當Server返回確認,併發送SYN,Client返回確認及SYN後,套接字處於ESTABLISHED階段,此時雙方的連線已經可以進行讀寫操作

   2)Server
當Server端呼叫socket函式呼叫時,相當於Server端產生了一個處於Closed狀態的監聽套接字
Server端呼叫
bind操作,將監聽套接字與指定的地址和埠關聯,然後又呼叫listen函式,系統會為其分配未完成佇列和完成佇列,此時的監聽套接字可以接受Client的連線,監聽套接字狀態處於LISTEN狀態。
當Server端呼叫accept操作時,會從完成佇列中取出一個已經完成的client連線,同時在server這段會產生一個會話套接字,用於和client端套接字的通訊,這個會話套接字的狀態是ESTABLISH。

 5. 連線關閉
與連線建立分為server/client不同,連線關閉並沒有絕對的server/client之分,連線關閉分為主動關閉和被動關閉。Server和client都可以擔任這兩個角色中的任意一個。如client可以關閉它與server的連線,同樣的server一樣也可以關閉一些長時間無讀寫事件發生的連線。既然這麼說了,下面就會分成兩個部分:client主動關閉,server主動關閉。

Client主動關閉,Server被動關閉:
Client主動關閉,Server被動關閉的情況還是蠻多的,比如說短連線中,當一次會話結束時,client就可以關閉它與server之間的連線。我們這邊直接說close而非shutdown。
當client想要關閉它與server之間的連線,首先client這邊會首先呼叫close函式,client端會發送一個FIN到server端,client端處於FIN_WAIT1狀態。當server端返回給client ACK後,client處於FIN_WAIT2狀態,server處於CLOSE_WAIT狀態。
當server端檢測到client端的關閉操作(read返回為0),server端也需要呼叫close操作,server端會向client端傳送一個FIN。此時server的狀態為LAST_ACK,當client收到來自server的FIN後,client端的套接字處於TIME_WAIT狀態,它會向server端再發送一個ack確認,此時server端收到ack確認後,此套接字處於CLOSED狀態。

Server端主動關閉的流程與Client端關閉類似,就不再多講,下面還需要關注的是TIME_WAIT這個狀態,分別按照client/server兩個部分講述。
首先說一下TCP/IP詳解中描述的關於TIME_WAIT的描述及其存在的必要性:
主動關閉的socket當收到對端的FIN操作後,該socket就會處於TIME_WAIT狀態,處於TIME_WAIT狀態的socket會存活2MSL(Max Segment Lifetime),之所以存活這麼長時間是有理由的:
一方面是可靠的實現TCP全雙工連線的終止,也就是當最後的ACK丟失後,被動關閉端會重發FIN,因此主動關閉端需要維持狀態資訊,以允許它重新發送最終的ACK。
另一方面TCP在2MSL等待期間,定義這個連線(4元組)不能再使用,任何遲到的報文都會丟棄。設想如果沒有2MSL的限制,恰好新到的連線正好滿足原先的4元組,這時候連線就可能接收到網路上的延遲報文就可能干擾最新建立的連線。
另一方面是為了重複的分節在網路中消逝。對於
另一方面是網路上仍然可能有殘餘的資料包
當Client主動關閉時,正常情況下client的socket會經歷TIME_WAIT的狀態

連線在關閉的時候,雙方都會有狀態的遷移,這就要求我們的程式中一定要有比較完全的


6. Server端的監聽套接字與會話套接字的不同:

當server端的socket呼叫bind和listen之後,監聽套接字的狀態就會變為LISTEN狀態,監聽套接字的工作決定了它只是監聽連線。
當server端呼叫accept,相當於從套接字的完成佇列中取出一個client的連線,此時可以確定
四元組,即:client的IP和port;server的IP和port,雙方各有一個套接字進行互動,這裡需要說一下server端的socket,我稱server端accept後產生的套接字為會話套接字,這個套接字一出生的狀態就是ESTABLISH
由server端得四元組我們可以看出,一個server從理論上可以接收的連線數量取決於檔案描述符的個數。