TCP連線——愛的傳聲筒

部落格園發表於2014-10-02

TCP通訊最重要的特徵是:有序(ordering)和可靠(reliable)。有序是通過將文字流分段並編號實現的。可靠是通過ACK回覆和重複傳送(retransmission)實現的。這一篇文章將引入TCP連線(connection)的概念。

TCP連線

網路層在邏輯上提供了埠的概念。一個IP地址可以有多個埠。一個具體的埠需要IP地址和埠號共同確定(我們記為IP:port的形式)。一個連線為兩個IP:port之間建立TCP通訊。(一個常用的比喻為:TCP連線就像兩個人打電話, IP為總機號碼,port為分機號碼)

參與連線的如果是兩臺電腦,那麼兩臺電腦作業系統的TCP模組負責建立連線。每個連線有四個引數(兩個IP,兩個埠),來表明“誰在和誰通話”。每臺電腦都會記錄有這四個引數,以確定是哪一個連線。如果這四個引數完全相同,則為同一連線;如果這四個引數有一個不同,即為不同的連線。這意味著,同一個埠上可以有多個連線。核心中的TCP模組生成連線之後,將連線分配給程式使用。

一個埠上可以有多個連線

一個埠上可以有多個連線

TCP連線是雙向(duplex)的。在TCP協議與”流”通訊中,我們所展示的TCP傳輸是單向的。雙向連線實際上就是建立兩個方向的TCP傳輸,所以概念上並不複雜。這時,連線的每一方都需要兩個滑窗,以分別處理髮送的文字流和接收的文字流。由於連線的雙向性,我們也要為兩個方向的文字流編號。這兩個文字流的編號相互獨立。為文字流分段和編號由傳送方來處理,回覆ACK則由接收的一方進行。

TCP片段的頭部格式

在深入TCP連線之前,我們需要對TCP片段的頭部格式有一些瞭解。我們知道,TCP片段分為頭部和資料。資料部分為TCP真正傳輸的文字流資料。下面為TCP片段的頭部格式:

來自wikipedia

先關注下面幾點:

1. 一個TCP頭部需要包含出發埠(source port)和目的地埠(destination port)。這些與IP頭中的兩個IP地址共同確定了連線。

2. 每個TCP片段都有序號(sequence number)。這些序號最終將資料部分的文字片段整理成為文字流。

3. ACK是一位(bit)。只有ACK位設定的時候,回覆號(Acknowledgement number)才有效。ACK回覆號說明了接收方期待接收的下一個片段,所以ACK回覆號為最後接收到的片段序號加1。

很多時候,ACK回覆“附著”在傳送的資料片段中。TCP協議是雙向的。比如A和B兩個電腦。ACK回覆是接收方回覆給傳送方 (比如A傳送給B, B回覆A)。但同時,B也可以是傳送方,B有可能有資料傳送給A,所以B就把ACK回覆附著在它要傳送給A的資料片段的頭部。這樣可以減少ACK所佔用的交通流量。一個片段可以只包含ACK回覆。一個純粹的ACK回覆片段不傳送文字流,所以不消耗序列號。如果有下一個正常的資料片段,它的序號將與純粹ACK回覆片段的序號相同。

(ACK回覆還可以“附著”在SYN片段和FIN片段)

4. ACK後面還有SYN和FIN,它們也各佔據一位(bit)。我將在後面說明這兩位。

連線的建立

在TCP協議與”流”通訊中討論的TCP傳輸需要一個前提:TCP連線已經建立。然而,TCP連線從無到有需要一個建立連線的過程。建立連線的最重要目是讓連線的雙方交換初始序號(ISN, Initial Sequence Number)。根據TCP協議的規定,文字流的第一個片段的序號不能是確定的數字(比如說1)。連線的雙方各自隨機生成自己的ISN,然後再利用的一定方式讓對方瞭解。這樣的規定是出於TCP連線安全考慮:如果以一個確定的數字作為初始的TCP序號,那麼其他人很容易猜出接下來的序列號,並按照正確的序號傳送“偽裝”的TCP片段,以插入到文字流中。

ISN交換是通過SYN片段實現的。SYN片段由頭部的SYN位表明,它的序號為傳送方的ISN。該片段由連線的一方首先發給給另一方,我們將傳送SYN的一方稱為客戶(client),而接收SYN的一方稱為伺服器(server)。我們使用ISN(c)表示client一方的ISN,使用ISN(s)表示server一方的ISN。隨後,接收到SYN的server需要回復ACK,併傳送出包含有server的ISN的SYN片段。下圖為建立連線的過程,也就是經典的TCP三次握手(three-way handshaking)。兩條豎直線分別為client和server的時間軸。每個箭頭代表了一次TCP片段的單向傳輸。

青色為純粹的ACK片段。整個過程的本質是雙方互發含有自己的ISN的SYN片段。根據TCP傳輸的規則,接收到ISN的一方需要回復ACK,所以共計四片資訊在建立連線過程中傳輸。之所以是三次握手 (而不是四次),是因為server將傳送SYN和回覆ACK合併到一個TCP片段中。我們以client方為例。client知道自己的ISN(也就是ISN(c))。建立連線之後,它也知道了對方的ISN(s)。此後,如果需要傳送文字流片段,則編號為ISN(c) + 1, ISN(c) + 2 …。如果接收文字流片段,則期待接收ISN(s) + 1, ISN(s) + 2 …。

連線建立之後,連線的雙方就可以按照TCP傳輸的方式相互傳送文字流了。

連線的正常終結

一個連線建立之後,連線兩端的程式可以利用該連線進行通訊。當連線的一方覺得“我講完了”,它可以終結連線中傳送到對方方向的通訊。連線最終通過四次握手(four-way handshaking)的方式終結,連線終結使用的是特殊片段FIN(FIN位為1的片段)。

我們可以看到,連線終結的過程中,連線雙方也交換了四片資訊(兩個FIN和兩個ACK)。在終結連線的過程中,TCP並沒有合併FIN與ACK片段。原因是TCP連線允許單向關閉(half-close)。也就是說,TCP連線關閉了一個方向的傳輸,成為一個單向連線(half-duplex)。第二個箭頭和第三個箭頭傳遞必須分開,才能有空隙在開放的方向上繼續傳輸。如果第二個箭頭和第三個箭頭合併在一起,那麼,隨著一方關閉,另一方也要被迫關閉。

第二和第三次握手之間,server可以繼續單向的傳送片段給client,但client不能傳送資料片段給server。

(上面的終結從client先發起,TCP連線終結也可以從server先發起。)

在Client傳送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複傳送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在傳送出ACK之後進入到TIME_WAIT狀態。Client會設定一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網路中最大的存活時間,2MSL就是一個傳送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連線。

 

TIME_WAIT State

總結

TCP是連線導向的協議,與之對應的是像UDP這樣的非連線導向的協議。連線能帶來更好的傳輸控制,但也需要更多額外的工作,比如連線的建立和終結。

我們還初步瞭解了TCP的頭部格式。應該注意到,許多時候我們將ACK片段“附著”在其他片段上。相對於純粹的ACK片段,我們這樣做節約了ACK所需的流量。事實上,由於ACK片段所需的ACK位和acknowledge number區域總是存在於TCP的頭部,所以附著ACK片段的成本基本上等於0。

相關文章