參照:
http://course.ccniit.com/CSTD/Linux/reference/files/018.PDF
http://hi.baidu.com/raycomer/item/944d23d9b502d13be3108f61
建立連接:
理解:窗口和滑動窗口
TCP的流量控制
連接建立時,各端分配一塊緩沖區用來存儲接收的數據,並將緩沖區的尺寸發送給另一端
接收方發送的確認信息中包含了自己剩余的緩沖區尺寸
剩余緩沖區空間的數量叫做窗口
2. TCP的流控過程(滑動窗口)
TCP(Transmission Control Protocol) 傳輸控制協議
三次握手
TCP是主機對主機層的傳輸控制協議,提供可靠的連接服務,采用三次握手確認建立一個連接:
位碼即tcp標志位,有6種標示:
SYN(synchronous建立聯機)
ACK(acknowledgement 確認)
PSH(push傳送)
FIN(finish結束)
RST(reset重置)
URG(urgent緊急)
Sequence number(順序號碼)
Acknowledge number(確認號碼)
客戶端TCP狀態遷移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服務器TCP狀態遷移:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
各個狀態的意義如下:
LISTEN - 偵聽來自遠方TCP端口的連接請求;
SYN-SENT -在發送連接請求后等待匹配的連接請求;
SYN-RECEIVED - 在收到和發送一個連接請求后等待對連接請求的確認;
ESTABLISHED- 代表一個打開的連接,數據可以傳送給用戶;
FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認;
FIN-WAIT-2 - 從遠程TCP等待連接中斷請求;
CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求;
CLOSING -等待遠程TCP對連接中斷的確認;
LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認;
TIME-WAIT -等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認;
CLOSED - 沒有任何連接狀態;
TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接,如圖1所示。
(1)第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。
(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(即接收方期望接收的下一個序列號)。
圖1 TCP三次握手建立連接
TCP的包頭結構:
源端口 16位
目標端口 16位
序列號 32位
回應序號 32位
TCP頭長度 4位
reserved 6位
控制代碼 6位
窗口大小 16位
偏移量 16位
校驗和 16位
選項 32位(可選)
這樣我們得出了TCP包頭的最小長度,為20字節
- 第一次握手:
客戶端發送一個TCP的SYN標志位置1的包指明客戶打算連接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段里。

- 第二次握手:
服務器發回確認包(ACK)應答。即SYN標志位和ACK標志位均為1同時,將確認序號(Acknowledgement Number)設置為客戶的I S N加1以.即X+1。
- 第三次握手.
客戶端再次發送確認包(ACK) SYN標志位為0,ACK標志位為1.並且把服務器發來ACK的序號字段+1,放在確定字段中發送給對方.並且在數據段放寫ISN的+1

(2)server端的TCP層對消息包進行分片傳輸;
(3)client端的TCP層對接收到的各個消息包分片回送響應;
(4)client端的TCP層每次收到一部分都會用ACK確認,之后server繼續傳輸,client繼續確認,直到完成響應消息的所有分片之后,Server發送組合HTTP響應包 200 OK,此時在client端的消息跟蹤中才可以顯示HTTP 200 OK的消息包
關閉連接:
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
CP的連接的拆除需要發送四個包,因此稱為四次揮手(four-way handshake)。客戶端或服務器均可主動發起揮手動作,在socket編程中,任何一方執行close()操作即可產生揮手操作。
(1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送。
(2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將占用一個序號。
(3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A。
(4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1。
TCP采用四次揮手關閉連接如圖2所示。
圖2 TCP四次揮手關閉連接
參見wireshark抓包,實測的抓包結果並沒有嚴格按揮手時序。我估計是時間間隔太短造成。

深入理解TCP連接的釋放:
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
TCP協議的連接是全雙工連接,一個TCP連接存在雙向的讀寫通道。
簡單說來是 “先關讀,后關寫”,一共需要四個階段。以客戶機發起關閉連接為例:
1.服務器讀通道關閉
2.客戶機寫通道關閉
3.客戶機讀通道關閉
4.服務器寫通道關閉
關閉行為是在發起方數據發送完畢之后,給對方發出一個FIN(finish)數據段。直到接收到對方發送的FIN,且對方收到了接收確認ACK之后,雙方的數據通信完全結束,過程中每次接收都需要返回確認數據段ACK。
詳細過程:
第一階段 客戶機發送完數據之后,向服務器發送一個FIN數據段,序列號為i;
1.服務器收到FIN(i)后,返回確認段ACK,序列號為i+1,關閉服務器讀通道;
2.客戶機收到ACK(i+1)后,關閉客戶機寫通道;
(此時,客戶機仍能通過讀通道讀取服務器的數據,服務器仍能通過寫通道寫數據)
第二階段 服務器發送完數據之后,向客戶機發送一個FIN數據段,序列號為j;
3.客戶機收到FIN(j)后,返回確認段ACK,序列號為j+1,關閉客戶機讀通道;
4.服務器收到ACK(j+1)后,關閉服務器寫通道。
這是標准的TCP關閉兩個階段,服務器和客戶機都可以發起關閉,完全對稱。
FIN標識是通過發送最后一塊數據時設置的,標准的例子中,服務器還在發送數據,所以要等到發送完的時候,設置FIN(此時可稱為TCP連接處於半關閉狀態,因為數據仍可從被動關閉一方向主動關閉方傳送)。如果在服務器收到FIN(i)時,已經沒有數據需要發送,可以在返回ACK(i+1)的時候就設置FIN(j)標識,這樣就相當於可以合並第二步和第三步。讀《Linux網絡編程》關閉TCP連接章節,作以下筆記:
TCP的TIME_WAIT和Close_Wait狀態
面試時看到應聘者簡歷中寫精通網絡,TCP編程,我常問一個問題,TCP建立連接需要幾次握手?95%以上的應聘者都能答對是3次。問TCP斷開連接需要幾次握手,70%的應聘者能答對是4次通訊。再問CLOSE_WAIT,TIME_WAIT是什么狀態,怎么產生的,對服務有什么影響,如何消除?有一部分同學就回答不上來。不是我扣細節,而是在通訊為主的前端服務器上,必須有能力處理各種TCP狀態。比如統計在本廠的一台前端機上高峰時間TCP連接的情況,統計命令:
- netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
結果:
除了ESTABLISHED,可以看到連接數比較多的幾個狀態是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就這幾個狀態的產生條件、對系統的影響以及處理方式進行簡單描述。
TCP狀態
TCP狀態如下圖所示:
可能有點眼花繚亂?再看看這個時序圖
下面看下大家一般比較關心的三種TCP狀態
SYN_RECV
服務端收到建立連接的SYN沒有收到ACK包的時候處在SYN_RECV狀態。有兩個相關系統配置:
1,net.ipv4.tcp_synack_retries :INTEGER
默認值是5
對於遠端的連接請求SYN,內核會發送SYN + ACK數據報,以確認收到上一個 SYN連接請求包。這是所謂的三次握手( threeway handshake)機制的第二個步驟。這里決定內核在放棄連接之前所送出的 SYN+ACK 數目。不應該大於255,默認值是5,對應於180秒左右時間。通常我們不對這個值進行修改,因為我們希望TCP連接不要因為偶爾的丟包而無法建立。
2,net.ipv4.tcp_syncookies
一般服務器都會設置net.ipv4.tcp_syncookies=1來防止SYN Flood攻擊。假設一個用戶向服務器發送了SYN報文后突然死機或掉線,那么服務器在發出SYN+ACK應答報文后是無法收到客戶端的ACK報文的(第三次握手無法完成),這種情況下服務器端一般會重試(再次發送SYN+ACK給客戶端)並等待一段時間后丟棄這個未完成的連接,這段時間的長度我們稱為SYN Timeout,一般來說這個時間是分鍾的數量級(大約為30秒-2分鍾)。
這些處在SYNC_RECV的TCP連接稱為半連接,並存儲在內核的半連接隊列中,在內核收到對端發送的ack包時會查找半連接隊列,並將符合的requst_sock信息存儲到完成三次握手的連接的隊列中,然后刪除此半連接。大量SYNC_RECV的TCP連接會導致半連接隊列溢出,這樣后續的連接建立請求會被內核直接丟棄,這就是SYN Flood攻擊。
能夠有效防范SYN Flood攻擊的手段之一,就是SYN Cookie。SYN Cookie原理由D. J. Bernstain和 Eric Schenk發明。SYN Cookie是對TCP服務器端的三次握手協議作一些修改,專門用來防范SYN Flood攻擊的一種手段。它的原理是,在TCP服務器收到TCP SYN包並返回TCP SYN+ACK包時,不分配一個專門的數據區,而是根據這個SYN包計算出一個cookie值。在收到TCP ACK包時,TCP服務器在根據那個cookie值檢查這個TCP ACK包的合法性。如果合法,再分配專門的數據區進行處理未來的TCP連接。
CLOSE_WAIT
發起TCP連接關閉的一方稱為client,被動關閉的一方稱為server。被動關閉的server收到FIN后,但未發出ACK的TCP狀態是CLOSE_WAIT。出現這種狀況一般都是由於server端代碼的問題,如果你的服務器上出現大量CLOSE_WAIT,應該要考慮檢查代碼。
TIME_WAIT
根據TCP協議定義的3次握手斷開連接規定,發起socket主動關閉的一方 socket將進入TIME_WAIT狀態。TIME_WAIT狀態將持續2個MSL(Max Segment Lifetime),在Windows下默認為4分鍾,即240秒。TIME_WAIT狀態下的socket不能被回收使用. 具體現象是對於一個處理大量短連接的服務器,如果是由服務器主動關閉客戶端的連接,將導致服務器端存在大量的處於TIME_WAIT狀態的socket, 甚至比處於Established狀態下的socket多的多,嚴重影響服務器的處理能力,甚至耗盡可用的socket,停止服務。
為什么需要TIME_WAIT?TIME_WAIT是TCP協議用以保證被重新分配的socket不會受到之前殘留的延遲重發報文影響的機制,是必要的邏輯保證。
和TIME_WAIT狀態有關的系統參數有一般由3個,本廠設置如下:
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_fin_timeout,默認60s,減小fin_timeout,減少TIME_WAIT連接數量。
net.ipv4.tcp_tw_reuse = 1表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認為0,表示關閉;
net.ipv4.tcp_tw_recycle = 1表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。
為了方便描述,我給這個TCP連接的一端起名為Client,給另外一端起名為Server。上圖描述的是Client主動關閉的過程,FTP協議中就這樣的。如果要描述Server主動關閉的過程,只要交換描述過程中的Server和Client就可以了,HTTP協議就是這樣的。
描述過程:
Client調用close()函數,給Server發送FIN,請求關閉連接;Server收到FIN之后給Client返回確認ACK,同時關閉讀通道(不清楚就去看一下shutdown和close的差別),也就是說現在不能再從這個連接上讀取東西,現在read返回0。此時Server的TCP狀態轉化為CLOSE_WAIT狀態。
Client收到對自己的FIN確認后,關閉 寫通道,不再向連接中寫入任何數據。
接下來Server調用close()來關閉連接,給Client發送FIN,Client收到后給Server回復ACK確認,同時Client關閉讀通道,進入TIME_WAIT狀態。
Server接收到Client對自己的FIN的確認ACK,關閉寫通道,TCP連接轉化為CLOSED,也就是關閉連接。
Client在TIME_WAIT狀態下要等待最大數據段生存期的兩倍,然后才進入CLOSED狀態,TCP協議關閉連接過程徹底結束。
以上就是TCP協議關閉連接的過程,現在說一下TIME_WAIT狀態。
從上面可以看到,主動發起關閉連接的操作的一方將達到TIME_WAIT狀態,而且這個狀態要保持Maximum Segment Lifetime的兩倍時間。為什么要這樣做而不是直接進入CLOSED狀態?
原因有二:
一、保證TCP協議的全雙工連接能夠可靠關閉
二、保證這次連接的重復數據段從網絡中消失
先說第一點,如果Client直接CLOSED了,那么由於IP協議的不可靠性或者是其它網絡原因,導致Server沒有收到Client最后回復的ACK。那么Server就會在超時之后繼續發送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連接,最后Server就會收到RST而不是ACK,Server就會以為是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP協議不符合可靠連接的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,當再次收到FIN的時候,能夠保證對方收到ACK,最后正確的關閉連接。
再說第二點,如果Client直接CLOSED,然后又再向Server發起一個新連接,我們不能保證這個新連接與剛關閉的連接的端口號是不同的。也就是說有可能新連接和老連接的端口號是相同的。一般來說不會發生什么問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接端口號是一樣的,如果前一次連接的某些數據仍然滯留在網絡中,這些延遲數據在建立新連接之后才到達Server,由於新連接和老連接的端口號是一樣的,又因為TCP協議判斷不同連接的依據是socket pair,於是,TCP協議就認為那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP連接還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連接的所有數據都從網絡中消失。
各種協議都是前人千錘百煉后得到的標准,規范。從細節中都能感受到精巧和嚴謹。每次深入都有同一個感覺,精妙。