TCP連接——愛的傳聲筒
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片段,以插入到文本流中。#p#
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。