淺析TCP/IP協議

深入理解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狀態


除了ESTABLISHED,可以看到連接數比較多的幾個狀態是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就這幾個狀態的產生條件、對系統的影響以及處理方式進行簡單描述。


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連接。

 

觀測服務上SYN_RECV連接個數爲:7314,對於一個高併發連接的通訊服務器,這個數字比較正常。

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,這樣可以保證本次連接的所有數據都從網絡中消失。

各種協議都是前人千錘百煉後得到的標準,規範。從細節中都能感受到精巧和嚴謹。每次深入都有同一個感覺,精妙。

TCP是如何保證數據可靠性的:

   TCP是一個可以保證可靠數據傳輸的傳輸層協議,主要採用採用以下四個機制實現數據可靠性傳輸:

    1.  字節編號機制:TCP數據段以字節爲單位對數據段的"數據"部分進行一一編號,確保每一個字節的數據都可以有序傳送和接收.
    2. 數據段確認機制:每接收一個數據段都必須有接收端向發送端返回確認數據段,其中的確認號表示已經正確接收的數據段序號.
    3. 超時重傳機制:TCP中有一個重傳定時器(RTT),發送一個數據段的同時也開啓這個定時器,如果定時器過期之時還沒有返回確認,則定時器停止,重傳該數據.
    4.  選擇性確認機制:(Selective ACK,SACK)/只重傳缺少部分的數據,不會重傳那些已經正確接收的數據.

TCP的流量控制:

    需要進行流量控制的原因就是,數據發送的太快,接收端來不及接收而出現的丟包狀況.流量控制的目的也就是不要讓發送端的發送的數據大於接收端的數據處理能力.

    TCP的流量控制通過滑動窗口機制來進行,窗口的大小的單位是字節.

    在TCP首部有一個窗口字段(見上圖TCP首部),這個字段的數值就是給對方設置的發送窗口的上限,發送窗口在連接時由雙方商定,但在通信過程中,接收端可以依據自己的資源狀況,動態的調整對方發送窗口的值的大小,達到控制的目的.

   

    假設每個字段大小爲100字節,當前發送窗口大小是400字節,發送端已經發送了400字節的數據,但是僅收到前200個字節的確認信息,還有200個字節沒有發來確認,那麼發送窗口當前時刻還能發送300字節,於是發送窗口前移,整個過程如圖所示.是一個簡單的滑動窗口的情況.

TCP的擁塞控制:

  什麼是網絡擁塞呢?我只發圖,不說話.

兩個方案:慢啓動與擁塞避免

    

    "擁塞窗口":是爲了避免發生擁塞而設置的窗口,最終發送的字節數是接收端爲發送端設置的"發送窗口"和"擁塞窗口"的最小值.

    "慢啓動閾值"(SSTHRESH):初始值是64k,即65535個字節,當發生一次數據丟失時,其值變爲"擁塞窗口"大小的一半.

    慢啓動:

      主機剛開始發送報文段時先將擁塞窗口的大小設置爲一個MSS(該連接上當前使用的最大數據段大小).

      每收到一個報文段的確認後,將擁塞窗口增加最多一個MSS的大小.

      以此類推,用這樣的方法逐步增大發送端擁塞窗口的大小,使分組注入到網絡的速率更加合理.

      直到擁塞窗口的值達到慢啓動閾值,這時候"擁塞避免"就發揮作用了.

    擁塞避免:

      該方案不再像慢啓動一樣以指數速度增長擁塞窗口的大小,而是到達慢啓動閾值後,按線性規律增長,是網絡比較不容易出現擁塞.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章