Stories

Detail Return Return

Android C++系列:Linux網絡(四)TCP詳解 - Stories Detail

1. tcp狀態轉換圖

這個圖N多人都知道,它排除和定位網絡或系統故障時大有幫助,但是怎樣牢牢地將這 張圖刻在腦中呢?那麼你就一定要對這張圖的每一個狀態,及轉換的過程有深刻 的認識, 不能只停留在一知半解之中。下面對這張圖的11種狀態詳細解析一下,以便加強記憶!不過在這之前,先回顧一下TCP建立連接的三次握手過程,以及關閉連接的四次握手過程。
image.png

1.1建立連接協議(三次握手)

  1. 客户端發送一個帶SYN標誌的TCP報文到服務器。這是三次握手過程中的報文1;
  2. 服務器端迴應客户端的,這是三次握手中的第2個報文,這個報文同時帶ACK標誌和SYN標誌。因此它表示對剛才客户端SYN報文的迴應;同時又標誌SYN給客户端,詢問客户 端是否準備好進行數據通訊;
  3. 客户必須再次迴應服務段一個ACK報文,這是報文段3。

1. 2連接終止協議(四次握手)

由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味着這 一方向上沒有數據流動,一個TCP連接在收到一個FIN後仍能發送數據。首先進行關閉的一方 將執行主動關閉,而另一方執行被動關閉。

  1. TCP客户端發送一個FIN,用來關閉客户到服務器的數據傳送(報文段4);
  2. 服務器收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。 和SYN一樣,一個FIN將佔用一個序號;
  3. 服務器關閉客户端的連接,發送一個FIN給客户端(報文段6);
  4. 客户段發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)。

1.3 11種狀態

  • 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. TCP流量控制(滑動窗口)

介紹UDP時我們描述了這樣的問題:如果發送端發送的速度較快,接收端接收到數據後處理的速度較慢,而接收緩衝區的大小是固定的,就會丟失數據。TCP協議通過’滑動窗口 (Sliding Window)’機制解決這一問題。看下圖的通訊過程。
image.png

  1. 發送端發起連接,聲明最大段尺寸是1460,初始序號是0,窗口大小是4K,表示“我 的接收緩衝區還有4K字節空閒,你發的數據不要超過4K”。接收端應答連接請求,聲明最大 段尺寸是1024,初始序號是8000,窗口大小是6K。發送端應答,三方握手結束。
  2. 發送端發出段4-9,每個段帶1K的數據,發送端根據窗口大小知道接收端的緩衝區滿了,因此停止發送數據。
  3. 接收端的應用程序提走2K數據,接收緩衝區又有了2K空閒,接收端發出段10,在應答已收到6K數據的同時聲明窗口大小為2K。
  4. 接收端的應用程序又提走2K數據,接收緩衝區有4K空閒,接收端發出段11,重新聲明窗口大小為4K。
  5. 發送端發出段12-13,每個段帶2K數據,段13同時還包含FIN位。
  6. 接收端應答接收到的2K數據(6145-8192),再加上FIN位佔一個序號8193,因此應答 序號是8194,連接處於半關閉狀態,接收端同時聲明窗口大小為2K。
  7. 接收端的應用程序提走2K數據,接收端重新聲明窗口大小為4K。
  8. 接收端的應用程序提走剩下的2K數據,接收緩衝區全空,接收端重新聲明窗口大小為 6K。
  9. 接收端的應用程序在提走全部數據後,決定關閉連接,發出段17包含FIN位,發送端應答,連接完全關閉。

上圖在接收端用小方塊表示1K數據,實心的小方塊表示已接收到的數據,虛線框表示接收緩衝區,因此套在虛線框中的空心小方塊表示窗口大小,從圖中可以看出,隨着應用程序 提走數據,虛線框是向右滑動的,因此稱為滑動窗口。

從這個例子還可以看出,發送端是一K一K地發送數據,而接收端的應用程序可以兩K兩 K地提走數據,當然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就 是説,應用程序所看到的數據是一個整體,或説是一個流(stream),在底層通訊中這些數 據可能被拆成很多數據包來發送,但是一個數據包有多少字節對應用程序是不可見的,因此 TCP協議是面向流的協議。而UDP是面向消息的協議,每個UDP段都是一條消息,應用程序必 須以消息為單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不同的。

3. TCP半鏈接狀態

當TCP鏈接中A發送FIN請求關閉,另一段B迴應ACK後,B沒有立即發送FIN給A時,A方處在半鏈接狀態,此時A可以接收B發送的數據,但是A已不能再向B發送數據。

#include <sys/socket.h>
int shutdown(int sockfd, int how);
  • sockfd: 需要關閉的socket的描述符
  • how:允許為shutdown操作選擇以下幾種方式:

    • SHUT_RD:關閉連接的讀端。也就是該套接字不再接受數據,任何當前在套接字接受緩衝區的數據將被丟棄。 進程將不能對該套接字發出任何讀操作。對 TCP套接字該調用之後接受到的任何數據將被確認然後無聲的丟棄掉。
    • SHUT_WR:關閉連接的寫端,進程不能在對此套接字發出寫操作
    • SHUT_RDWR:相當於調用shutdown兩次:首先是以SHUT_RD,然後以SHUT_WR

使用close中止一個連接,但它只是減少描述符的參考數,並不直接關閉連接,只有當描述符的參考數為0時才關閉連接。 shutdown可直接關閉描述符,不考慮描述 符的參考 數,可選擇中止一個方向的連接。

注意:

  1. 如果有多個進程共享一個套接字,close每被調用一次,計數減1,直到計數為0時,也就是所用進程都調用了close,套接字將被釋放;
  2. 在多進程中如果一個進 程中shutdown(sfd, SHUT_RDWR)後其它的進程將無法進行通信. 如果一個進程close(sfd) 將不會影響到其它進程. 得自己理解引用計數的用法了。

4. 2MSL

MSL是Maximum Segment Lifetime英文的縮寫,中文可以譯為“報文最大生存時間”,2MSL即兩倍的MSLTCPTIME_WAIT狀態也稱為2MSL等待狀態。

2MSL TIME_WAIT狀態存在的理由:

TIME_WAIT狀態的存在有兩個理由:

  1. 讓4次握手關閉流程更加可靠;4次握手的最後一個ACK是是由主動關閉方發送出去的,若這個ACK丟失,被動關閉方會再次發一個FIN過來。若主動關閉方能夠保持一個2MSL的TIME_WAIT狀態,則有更大的機會讓丟失的ACK 被再次發送出去;
  2. 防止lost duplicate對後續新建正常鏈接的傳輸造成破壞。lost duplicate在實際的網絡中非常常見,經常是由於路由器產生故障,路徑無法收斂,導致一個packet在路由器A,B,C之間做類似死循環的跳轉。IP頭部有個TTL,限制了一個包在網絡中的最大跳數,因此這個包有兩種命運,要麼最後TTL變為0,在網絡中消失;要麼TTL在 變為0之前路由器路徑收斂,它憑藉剩餘的TTL跳數終於到達目的地。但非常可惜的是TCP通過超時重傳機制在早些時候發送了一個跟它一模一樣的包,並先於它達到了目的地,因此它的命運也就註定被TCP協議棧拋棄。另外一個概念叫做incarnation connection,指跟上次的socket pair一摸一樣的新連接,叫做incarnation of previous connection。lost duplicate加上incarnation connection,則會對我們的傳輸造成致命的錯誤。大家都知道 TCP是流式的,所有包到達的順序是不一致的,依靠序列號由TCP協議棧做順序的拼接;假設一個incarnation connection這時收到的seq=1000, 來了一個lost duplicate為seq=1000, len=1000, 則tcp認為這個lost duplicate合法,並存放入了receive buffer,導致傳輸出 現錯誤。通過一個2MSL TIME_WAIT狀態,確保所有的lost duplicate都會消失掉,避免對新連接造成錯誤。

該狀態為什麼設計在主動關閉這一方:

  1. 發最後ack的是主動關閉一方;
  2. 只要有一方保持TIME_WAIT狀態,就能起到避免incarnation connection在2MSL內的重新建立,不需要兩方都有如何正確對待2MSL TIME_WAIT?

RFC要求socket pair在處於TIME_WAIT時,不能再起一個incarnation connection。但絕大部分TCP實現,強加了更為嚴格的限制。在2MSL等待期間,socket中使用的本地端口在默認情況下不能再被使用。若A 10.234.5.5:1234 和B 10.55.55.60:6666建立了連接,A主動關閉,那麼在A端只要port為1234,無論對方的port和ip是什麼,都不允許再起服務。顯而易見這是比RFC更為嚴格的限制,RFC僅僅是要求socket pair不一致,而實現當中只要這個 port處於TIME_WAIT,就不允許起連接。這個限制對主動打開方來説是無所謂的,因為一般用的是臨時端口;但對於被動打開方,一般是server,就悲劇了,因為server一般是熟知端口。比如http,一般端口是80,不可能允許這個服務在2MSL內不能起來。解決方案是給服務器的socket設置SO_REUSEADDR選項,這樣的話就算熟知端口處於 TIME_WAIT狀態,在這個端口上依舊可以將服務啓動。當然,雖然有了SO_REUSEADDR選項,但sockt pair這個限制依舊存在。比如上面的例子,A通過SO_REUSEADDR選項依舊在1234端口上起了監聽,但這時我們若是從B通過6666端 口去連它,TCP協議會告訴我們連接失敗,原因為Address already in use.

5. 總結

本文介紹了TCP的三次握手、四次揮手、11種狀態、TCP滑動窗口流量控制、TCP半連接狀態以及TCP TIME_WAIT兩倍報文最大生存時長。

user avatar sherlocked93 Avatar coulthard Avatar samhou Avatar u_9449786 Avatar wenzhongdejianpan Avatar ydswin Avatar puxiaoke6 Avatar lazytimes Avatar lvweifu Avatar startshineye Avatar kangkaidesuancaiyu Avatar jibianoububian Avatar
Favorites 23 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.