TCP狀態裝換圖
[TOC]
狀態圖
狀態解釋
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(重要、共詳細的請看下圖的2MSL): 表示收到了對方的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可用狀態了。
tcp連接的建立3次握手
服務器端通常處于監聽狀態即LISTEN,accept()處于阻塞狀態 當客戶端連接服務器端時此時客戶端的connect()剛剛調用并處于阻塞狀態。
將會觸發以下事件:
首先客戶端的應用程序將會使tcp進程發送SYN,MSS(最大報文段長度),此時客戶端將會處于SYN_SENT 網絡傳輸給服務器端后;
服務器端tcp接收到后服務器端將會由LISTEN狀態變為SYN_RCVD 然后服務器端也會發送一個SYN,MSS還有一個ACK,注意這個ACK是客戶端發送的SYN值加1;
客戶端在接收到服務器端的SYN,MSS,ACK核對無誤后將會由SYN_SENT狀態變為ESTABLILSHED;
此時客戶端的connect()函數將會返回不再處于阻塞狀態,同時客戶端發送ACK,此ACK是服務器端發送的SYN值加1,服務器端在接收到客戶端的ACK核對無誤后,accept()將從阻塞狀態返回,同時read()處于阻塞狀態。此時連接已經建立。
建立連接協議(三次握手)
(1)客戶端發送一個帶SYN標志的TCP報文到服務器。這是三次握手過程中的報文1。
(2) 服務器端回應客戶端的,這是三次握手中的第2個報文,這個報文同時帶ACK標志和SYN標志。因此它表示對剛才客戶端SYN報文的回應;同時又標志SYN給客戶端,詢問客戶端是否準備好進行數據通訊。
(3) 客戶必須再次回應服務段一個ACK報文,這是報文段3。
tcp斷開連接4次揮手
當一端數據已經發送完了,就會將本端的tcp斷開掉通常是客戶端主動斷開,這種情況同時是客戶端應用程序調用close(fd)關閉套接字,這將觸發tcp進程發送 FIN,此時客戶端將會處于FIN_WAIT_1,服務器端在接收到這個FIN后將會處于close_wait()狀態,同時read()return 0,然后服務器端將會發送ACK 值為客戶端發送的FIN值加1,客戶端在接收到 服務器端發送給它的ACK后將會處于FIN_WAIT2然后服務器端將 客戶端的文件描述符讀端關閉,此時服務器端可能還會有未發送的數據,通常會悄悄丟棄掉,然后 關閉客戶端描述符close(),然后服務器端tcp進程將會發送FIN此時服務器端將會處于LAST_ACK狀態客戶端在接收到服務器端發送的FIN后將會由FIN_WAIT2狀態變TIME_WAIT狀態,同時發送ACK值為 客戶端發送的FIN值加1,服務器端在接收到客戶端發送的ACK后核對無誤后將由LAST_ACK狀態變為 CLOSED狀態。注意客戶端在處于TIME_WAIT狀態時要經歷2個MSL時間才會將狀態變為CLOSED通常這個 等待的時間為60秒。
連接終止協議(四次握手)
由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1) TCP客戶端發送一個FIN,用來關閉客戶到服務器的數據傳送(報文段4)。
(2) 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。
(3) 服務器關閉客戶端的連接,發送一個FIN給客戶端(報文段6)。
(4) 客戶段發回ACK報文確認,并將確認序號設置為收到序號加1(報文段7)。
TCP正常連接建立和終止所對用的狀態
同時打開期間報文段的交換![]
同時關閉期間的報文段交換
總結
TCP是一個面向連接的協議,所以在連接雙方發送數據之前,都需要首先建立一條連接。
由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
服務器調?socket()、 bind()、 listen() 完成初始化后,調?accept()阻塞等待,處于監聽端口的狀態,客戶端調?socket()初始化后,調?connect()發出SYN段并阻塞等待服務器應答,服務器應答?個SYN-ACK段,客戶端收到后從connect()返回,同時應答?個ACK段,服務器收到后 從accept()返回。
數據傳輸的過程: 建立連接后,TCP協議提供全雙?的通信服務,但是?般的客戶端/服務器程序的流程是由客戶端主 動發起請求,服務器被動處理請求,?問?答的?式。因此,服務器從accept()返回后立刻調 ?read(),讀socket就像讀管道?樣,如果沒有數據到達就阻塞等待,這時客戶端調?write()發送 請求給服務器,服務器收到后從read()返回,對客戶端的請求進?處理,在此期間客戶端調 ?read()阻塞等待服務器的應答,服務器調?write()將處理結果發回給客戶端,再次調?read()阻塞 等待下?條請求,客戶端收到后從read()返回,發送下?條請求,如此循環下去。如果客戶端沒有更多的請求了,就調?close() 關閉連接,就像寫端關閉的管道?樣,服務器 的read()返回0,這樣服務器就知道客戶端關閉了連接,也調?close()關閉連接。注意,任何??調?close() 后,連接的兩個傳輸?向都關閉,不能再發送數據了。如果??調?shutdown() 則連接處 于半關閉狀態,仍可接收對?發來的數據。
詳情:基于TCP協議的服務器/客戶端程序
TCP連接的建立可以簡單的稱為三次握手,而連接的中止則可以叫做四次握手。
1.連接的建立
在建立連接的時候,客戶端首先向服務器申請打開某一個端口(用SYN段等于1的TCP報文),然后服務器端發回一個ACK報文通知客戶端請求報文收到,客戶端收到確認報文以后再次發出確認報文確認剛才服務器端發出的確認報文(繞口么),至此,連接的建立完成。這就叫做三次握手。如果打算讓雙方都做好準備的話,一定要發送三次報文,而且只需要三次報文就可以了。
可以想見,如果再加上TCP的超時重傳機制,那么TCP就完全可以保證一個數據包被送到目的地。
2.結束連接
TCP有一個特別的概念叫做half-close,這個概念是說,TCP的連接是全雙工(可以同時發送和接收)連接,因此在關閉連接的時候,必須關閉傳和送兩個方向上的連接??蛻魴C給服務器一個FIN為1的TCP報文,然后服務器返回給客戶端一個確認ACK報文,并且發送一個FIN報文,當客戶機回復ACK報文后(四次握手),連接就結束了。
3.最大報文長度
在建立連接的時候,通信的雙方要互相確認對方的最大報文長度(MSS),以便通信。一般這個SYN長度是MTU減去固定IP首部和TCP首部長度。對于一個以太網,一般可以達到1460字節。當然如果對于非本地的IP,這個MSS可能就只有536字節,而且,如果中間的傳輸網絡的MSS更佳的小的話,這個值還會變得更小。
4.TCP的狀態遷移圖
如上圖所示:服務器的狀態遷移和客戶端的狀態遷移,如果從某一個角度出發來看這個圖,就會清晰許多,這里面的服務器和客戶端都不是絕對的,發送數據的就是客戶端,接受數據的就是服務器。
4.1.客戶端應用程序的狀態遷移圖
客戶端的狀態可以用如下的流程來表示:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
以上流程是在程序正常的情況下應該有的流程,從書中的圖中可以看到,在建立連接時,當客戶端收到SYN報文的ACK以后,客戶端就打開了數據交互地連接。而結束連接則通常是客戶端主動結束的,客戶端結束應用程序以后,需要經歷FIN_WAIT_1,FIN_WAIT_2等狀態,這些狀態的遷移就是前面提到的結束連接的四次握手。
4.2.服務器的狀態遷移圖
服務器的狀態可以用如下的流程來表示:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
在建立連接的時候,服務器端是在第三次握手之后才進入數據交互狀態,而關閉連接則是在關閉連接的第二次握手以后(注意不是第四次)。而關閉以后還要等待客戶端給出最后的ACK包才能進入初始的狀態。
4.3.其他狀態遷移
書中的圖還有一些其他的狀態遷移,這些狀態遷移針對服務器和客戶端兩方面的總結如下
LISTEN->SYN_SENT,對于這個解釋就很簡單了,服務器有時候也要打開連接的嘛。
SYN_SENT->SYN收到,服務器和客戶端在SYN_SENT狀態下如果收到SYN數據報,則都需要發送SYN的ACK數據報并把自己的狀態調整到SYN收到狀態,準備進入ESTABLISHED
SYN_SENT->CLOSED,在發送超時的情況下,會返回到CLOSED狀態。
SYN_收到->LISTEN,如果受到RST包,會返回到LISTEN狀態。
SYN_收到->FIN_WAIT_1,這個遷移是說,可以不用到ESTABLISHED狀態,而可以直接跳轉到FIN_WAIT_1狀態并等待關閉。
4.4.2MSL等待狀態
書中給的圖里面,有一個TIME_WAIT等待狀態,這個狀態又叫做2MSL狀態,說的是在TIME_WAIT2發送了最后一個ACK數據報以后,要進入TIME_WAIT狀態,這個狀態是防止最后一次握手的數據報沒有傳送到對方那里而準備的(注意這不是四次握手,這是第四次握手的保險狀態)。這個狀態在很大程度上保證了雙方都可以正常結束,但是,問題也來了。
由于插口的2MSL狀態(插口是IP和端口對的意思,socket),使得應用程序在2MSL時間內是無法再次使用同一個插口的,對于客戶程序還好一些,但是對于服務程序,例如httpd,它總是要使用同一個端口來進行服務,而在2MSL時間內,啟動httpd就會出現錯誤(插口被使用)。為了避免這個錯誤,服務器給出了一個平靜時間的概念,這是說在2MSL時間內,雖然可以重新啟動服務器,但是這個服務器還是要平靜的等待2MSL時間的過去才能進行下一次連接。
4.5.FIN_WAIT_2狀態
這就是著名的半關閉的狀態了,這是在關閉連接時,客戶端和服務器兩次握手之后的狀態。在這個狀態下,應用程序還有接受數據的能力,但是已經無法發送數據,但是也有一種可能是,客戶端一直處于FIN_WAIT_2狀態,而服務器則一直處于WAIT_CLOSE狀態,而直到應用層來決定關閉這個狀態。
5.RST,同時打開和同時關閉
RST是另一種關閉連接的方式,應用程序應該可以判斷RST包的真實性,即是否為異常中止。而同時打開和同時關閉則是兩種特殊的TCP狀態,發生的概率很小。
6.TCP服務器設計
前面曾經講述過UDP的服務器設計,可以發現UDP的服務器完全不需要所謂的并發機制,它只要建立一個數據輸入隊列就可以。但是TCP不同,TCP服務器對于每一個連接都需要建立一個獨立的進程(或者是輕量級的,線程),來保證對話的獨立性。所以TCP服務器是并發的。而且TCP還需要配備一個呼入連接請求隊列(UDP服務器也同樣不需要),來為每一個連接請求建立對話進程,這也就是為什么各種TCP服務器都有一個最大連接數的原因。而根據源主機的IP和端口號碼,服務器可以很輕松的區別出不同的會話,來進行數據的分發。
備注:
TCP指的是傳輸控制協議。它是一種面向連接導向的、可靠地及基于字節流的運輸層通信協議。而在接觸TCP中還有UDP,UDP也是一項重要的傳輸協議。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
TCP流量控制(滑動窗口)
介紹UDP時我們描述了這樣的問題:如果發送端發送的速度較快,接收端接收到數據后處理的速度較慢,而接收緩沖區的大小是固定的,就會丟失數據。TCP協議通過“滑動窗口(Sliding Window)”機制(防止出現丟包)解決這一問題。具體請看(http://www.cnblogs.com/invisible2/p/6650289.html)
TCP協議作為一個可靠的面向流的傳輸協議,其可靠性和流量控制由滑動窗口協議保證,而擁塞控制則由控制窗口結合一系列的控制算法實現。
流量控制:端到端的控制方式,接收方傳遞信息給發送方,使其不要發送數據太快。
主要的方式就是返回的ACK中會包含自己的接收窗口的大小,并且利用大小來控制發送方的數據發送
擁塞控制:就是防止過多的數據注入到網絡中,這樣可以使網絡中的路由器或鏈路不致過載。
常用的方法就是:
- 慢開始、擁塞控制(發送試探報文)
- 快重傳、快恢復
問題
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報文。
參考資料:
4:TCP狀態轉換圖