TCP协议

**傳輸控制協定**（英語：**T**ransmission **C**ontrol **P**rotocol，縮寫：**TCP**）是一種連接導向的、可靠的、基於位元組流的傳輸層通信協定，由 IETF 的 RFC 793 定義。在簡化的電腦網路 OSI 模型中，它完成第四層傳輸層所指定的功能。使用者資料報協定（UDP）是同一層內另一個重要的傳輸協定。

在網際網路協定族（Internet protocol suite）中，TCP 層是位於 IP 層之上，應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接，但是 IP 層不提供這樣的流機制，而是提供不可靠的包交換。

應用層向 TCP 層傳送用於網間傳輸的、用 8 位元位元組表示的資料流，然後 TCP 把資料流分割成適當長度的報文段（通常受該電腦連接的網路的資料鏈路層的最大傳輸單元（MTU）的限制）。之後 TCP 把結果包傳給 IP 層，由它來透過網路將包傳送給接收端實體的 TCP 層。TCP 為了保證不發生丟包，就給每個包一個序號，同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認資訊（ACK）；如果傳送端實體在合理的往返時延（RTT）內未收到確認，那麼對應的封包就被假設為已遺失並進行重傳。TCP 用一個校驗和函式來檢驗資料是否有錯誤，在傳送和接收時都要計算校驗和。

## 簡介

資料在 TCP 層稱為流（Stream），資料分組稱為分段（Segment）。作為比較，資料在 IP 層稱為 Datagram，資料分組稱為分片（Fragment）。 UDP 中分組稱為 Message。

## 運作方式

TCP 協定的執行可劃分為三個階段：連接建立 (_connection establishment_)、資料傳送（_data transfer_）和連接終止（_connection termination_）。作業系統將 TCP 連接抽象為通訊端表示的本地端點（local end-point），作為編程介面給程式使用。在 TCP 連接的生命期內，本地端點要經歷一系列的狀態改變。

### 建立通路

TCP 用三路握手（或稱三次握手，three-way handshake）過程建立一個連接。在連接建立過程中，很多參數要被初始化，例如序號被初始化以保證按序傳輸和連接的強壯性。

一對終端同時初始化一個它們之間的連接是可能的。但通常是由一端（伺服器端）打開一個通訊端（socket）然後監聽來自另一方（客戶端）的連接，這就是通常所指的被動打開（passive open）。伺服器端被被動打開以後，客戶端就能開始建立主動打開（active open）。

伺服器端執行了 listen 函式後，就在伺服器上建立起兩個佇列：

* SYN 佇列：存放完成了二次握手的結果。 佇列長度由 listen 函式的參數 backlog 指定。
* ACCEPT 佇列：存放完成了三次握手的結果。佇列長度由 listen 函式的參數 backlog 指定。

三次握手協定的過程：

1. 客戶端（通過執行 connect 函式）向伺服器端傳送一個 SYN 包，請求一個主動打開。該包攜帶客戶端為這個連接請求而設定的亂數 **A** 作為訊息序列號。
2. 伺服器端收到一個合法的 SYN 包後，把該包放入 SYN 佇列中；回送一個 SYN/ACK。ACK 的確認碼應為 **A+1**，SYN/ACK 包本身攜帶一個隨機產生的序號 **B**。
3. 客戶端收到 SYN/ACK 包後，傳送一個 ACK 包，該包的序號被設定為 **A+1**，而 ACK 的確認碼則為 **B+1**。然後客戶端的 connect 函式成功返回。當伺服器端收到這個 ACK 包的時候，把請求影格從 SYN 佇列中移出，放至 ACCEPT 佇列中；這時 accept 函式如果處於阻塞狀態，可以被喚醒，從 ACCEPT 佇列中取出 ACK 包，重新建立一個新的用於雙向通信的 sockfd，並返回。

如果伺服器端接到了客戶端發的 SYN 後回了 SYN-ACK 後客戶端斷線了，伺服器端沒有收到客戶端回來的 ACK，那麼，這個連接處於一個中間狀態，既沒成功，也沒失敗。於是，伺服器端如果在一定時間內沒有收到的 TCP 會重發 SYN-ACK。在 Linux 下，預設重試次數為 5 次，重試的間隔時間從 1s 開始每次都翻倍，5 次的重試時間間隔為 1s, 2s, 4s, 8s, 16s，總共 31s，第 5 次發出後還要等 32s 才知道第 5 次也逾時了，所以，總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s，TCP 才會斷開這個連接。使用三個 TCP 參數來調整行為：tcp\_synack\_retries 減少重試次數；tcp\_max\_syn\_backlog，增大 SYN 連接數；tcp\_abort\_on\_overflow 決定超出能力時的行為。

「三次握手」的目的是「為了防止已失效的連接 (connect) 請求報文段傳送到了伺服器端，因而產生錯誤」，也即為了解決「網路中存在延遲的重複分組」問題。例如：client 發出的第一個連接請求報文段並沒有遺失，而是在某個網路結點長時間的滯留了，以致延誤到連接釋放以後的某個時間才到達 server。本來這是一個早已失效的報文段。但 server 收到此失效的連接請求報文段後，就誤認為是 client 發出的一個新的連接請求。於是就向 client 發出確認報文段，同意建立連接。假設不採用「三次握手」，那麼只要 server 發出確認，新的連接就建立了。由於現在 client 並沒有發出建立連接的請求，因此不會理睬 server 的確認，也不會向 server 傳送資料。但 server 卻以為新的運輸連接已經建立，並一直等待 client 發來資料。這樣，server 的很多資源就白白浪費掉了。採用「三次握手」的辦法可以防止上述現象發生，client 不會向 server 的確認發出確認。server 由於收不到確認，就知道 client 並沒有要求建立連接。

### 資源使用

主機收到一個 TCP 包時，用兩端的 IP 位址與埠號來標識這個 TCP 包屬於哪個 session。使用一張表來儲存所有的 session，表中的每條稱作 Transmission Control Block（TCB），tcb 結構的定義包括連接使用的源埠、目的埠、目的 ip、序號、應答序號、對方窗口大小、己方窗口大小、tcp 狀態、tcp 輸入 / 輸出佇列、應用層輸出佇列、tcp 的重傳有關變數等。

伺服器端的連接數量是無限的，只受記憶體的限制。客戶端的連接數量，過去由於在傳送第一個 SYN 到伺服器之前需要先分配一個隨機空閒的埠，這限制了客戶端 IP 位址的對外發出連接的數量上限。從 Linux 4.2 開始，有了 socket 選項 IP\_BIND\_ADDRESS\_NO\_PORT，它通知 Linux 核心不保留 usingbind 使用埠號為 0 時內部使用的臨時埠（ephemeral port），在 connect 時會自動選擇埠以組成獨一無二的四元組（同一個客戶端埠可用於連接不同的伺服器通訊端；同一個伺服器埠可用於接受不同客戶端通訊端的連接）。

對於不能確認的包、接收但還沒讀取的資料，都會占用作業系統的資源。

### 資料傳輸

在 TCP 的資料傳送狀態，很多重要的機制保證了 TCP 的可靠性和強壯性。它們包括：使用序號，對收到的 TCP 報文段進行排序以及檢測重複的資料；使用校驗和檢測報文段的錯誤，即無錯傳輸；使用確認和計時器來檢測和糾正丟包或延時；流控制（Flow control）；擁塞控制（Congestion control）；遺失包的重傳。

#### 可靠傳輸

通常在每個 TCP 報文段中都有一對序號和確認號。TCP 報文傳送者稱自己的位元組流的編號為序號（_sequence number_），稱接收到對方的位元組流編號為確認號。TCP 報文的接收者為了確保可靠性，在接收到一定數量的連續位元組流後才傳送確認。這是對 TCP 的一種擴充，稱為選擇確認（Selective Acknowledgement）。選擇確認使得 TCP 接收者可以對亂序到達的資料塊進行確認。每一個位元組傳輸過後，SN 號都會遞增 1。

通過使用序號和確認號，TCP 層可以把收到的報文段中的位元組按正確的順序交付給應用層。序號是 32 位元的無符號數，在它增大到 232-1 時，便會迴繞到 0。對於初始化序列號 (ISN) 的選擇是 TCP 中關鍵的一個操作，它可以確保強壯性和安全性。

TCP 協定使用序號標識每端發出的位元組的順序，從而另一端接收資料時可以重建順序，無懼傳輸時的包的亂序交付或丟包。在傳送第一個包時（SYN 包），選擇一個亂數作為序號的初值，以克制 TCP 序號預測攻擊.

傳送確認包（Acks），攜帶了接收到的對方發來的位元組流的編號，稱為確認號，以告訴對方已經成功接收的資料流的位元組位置。Ack 並不意味著資料已經交付了上層應用程式。

可靠性通過傳送方檢測到遺失的傳輸資料並重傳這些資料。包括逾時重傳（Retransmission timeout，RTO）與重複累計確認（duplicate cumulative acknowledgements，DupAcks）。

##### 基於重複累計確認的重傳

如果一個包（不妨設它的序號是 100，即該包始於第 100 位元組）遺失，接收方就不能確認這個包及其以後的包，因為採用了累計 ack。接收方在收到 100 以後的包時，發出對包含第 99 位元組的包的確認。這種重複確認是包遺失的訊號。傳送方如果收到 3 次對同一個包的確認，就重傳最後一個未被確認的包。閾值設為 3 被證實可以減少亂序包導致的無作用的重傳（spurious retransmission）現象。 選擇性確認（SACK）的使用能明確回饋哪個包收到了，極大改善了 TCP 重傳必要的包的能力。

##### 逾時重傳

傳送方使用一個保守估計的時間作為收到封包的確認的逾時上限。如果超過這個上限仍未收到確認包，傳送方將重傳這個封包。每當傳送方收到確認包後，會重設這個重傳定時器。典型地，定時器的值設定為 ${\text{smoothed RTT}}+\max(G,4\times {\text{RTT variation}})$ 其中 $G$ 是時鐘粒度。 進一步，如果重傳定時器被觸發，仍然沒有收到確認包，定時器的值將被設為前次值的二倍（直到特定閾值）。這是由於存在一類通過欺騙傳送者使其重傳多次，進而壓垮接收者的攻擊，而使用前述的定時器策略可以避免此類中間人攻擊方式的阻斷服務攻擊。

#### 資料傳輸舉例

1. 傳送方首先傳送第一個包含序列號為 1（可變化）和 1460 位元組資料的 TCP 報文段給接收方。接收方以一個沒有資料的 TCP 報文段來回覆（只含報頭），用確認號 1461 來表示已完全收到並請求下一個報文段。
2. 傳送方然後傳送第二個包含序列號為 1461，長度為 1460 位元組的資料的 TCP 報文段給接收方。正常情況下，接收方以一個沒有資料的 TCP 報文段來回覆，用確認號 2921（1461+1460）來表示已完全收到並請求下一個報文段。傳送接收這樣繼續下去。
3. 然而當這些封包都是相連的情況下，接收方沒有必要每一次都回應。比如，他收到第 1 到 5 條 TCP 報文段，只需回應第五條就行了。在例子中第 3 條 TCP 報文段被遺失了，所以儘管他收到了第 4 和 5 條，然而他只能回應第 2 條。
4. 傳送方在傳送了第三條以後，沒能收到回應，因此當時鐘（timer）過時（expire）時，他重發第三條。（每次傳送者傳送一條 TCP 報文段後，都會再次啟動一次時鐘：RTT）。
5. 這次第三條被成功接收，接收方可以直接確認第 5 條，因為 4，5 兩條已收到。

#### 校驗和

TCP 的 16 位元的校驗和（checksum）的計算和檢驗過程如下：傳送者將 TCP 報文段的頭部和資料部分的和計算出來，再對其求一補數（一的補數），就得到了校驗和，然後將結果裝入報文中傳輸。（這裡用一補數和的原因是這種方法的迴圈進位使校驗和可以在 16 位元、32 位元、64 位元等情況下的計算結果再疊加後相同）接收者在收到報文後再按相同的演算法計算一次校驗和。這裡使用的一補數使得接收者不用再將校驗和欄位儲存起來後清零，而可以直接將報文段連同校驗加總。如果計算結果是全部為一，那麼就表示了報文的完整性和正確性。

注意：TCP 校驗和也包括了 96 位的偽頭部，其中有源位址、目的位址、協定以及 TCP 的長度。這可以避免報文被錯誤地路由。

按現在的標準，TCP 的校驗和是一個比較脆弱的校驗。出錯概率高的資料鏈路層需要更高的能力來探測和糾正連接錯誤。TCP 如果是在今天設計的，它很可能有一個 32 位元的 CRC 校驗來糾錯，而不是使用校驗和。但是通過在第二層使用通常的 CRC 校驗或更完全一點的校驗可以部分地彌補這種脆弱的校驗。第二層是在 TCP 層和 IP 層之下的，比如 PPP 或乙太網路，它們使用了這些校驗。但是這也並不意味著 TCP 的 16 位元校驗和是冗餘的，對於網際網路傳輸的觀察，表明在受 CRC 校驗保護的各跳之間，軟體和硬體的錯誤通常也會在報文中引入錯誤，而端到端的 TCP 校驗能夠捕捉到大部分簡單的錯誤。 這就是應用中的端到端原則。

#### 流量控制

流量控制用來避免主機分組傳送得過快而使接收方來不及完全收下，一般由接收方通告給傳送方進行調控。

TCP 使用滑動窗口協定實現流量控制。接收方在「接收窗口」域指出還可接收的位元組數量。傳送方在沒有新的確認包的情況下至多傳送「接收窗口」允許的位元組數量。接收方可修改「接收窗口」的值。

當接收方宣布接收窗口的值為 0，傳送方停止進一步傳送資料，開始了「保持定時器」（persist timer），以避免因隨後的修改接收窗口的封包遺失使連接的雙側進入死結，傳送方無法發出資料直至收到接收方修改窗口的指示。當「保持定時器」到期時，TCP 傳送方嘗試恢復傳送一個小的 ZWP 包（Zero Window Probe），期待接收方回覆一個帶著新的接收窗口大小的確認包。一般 ZWP 包會設定成 3 次，如果 3 次過後還是 0 的話，有的 TCP 實現就會發 RST 把連結斷了。

如果接收方以很小的增量來處理到來的資料，它會發布一系列小的接收窗口。這被稱作愚蠢窗口綜合症，因為它在 TCP 的封包中傳送很少的一些位元組，相對於 TCP 包頭是很大的開銷。解決這個問題，就要避免對小的 window size 做出回應，直到有足夠大的 window size 再回應：

* 接收端使用 David D Clark 演算法：如果收到的資料導致 window size 小於某個值，可以直接 ack 把 window 給關閉了，阻止了傳送端再發資料。等到接收端處理了一些資料後 windows size 大於等於了 MSS，或者接收端 buffer 有一半為空，就可以把 window 打開讓傳送端再發資料過來。
* 傳送端使用 Nagle 演算法來延時處理，條件一：Window Size>=MSS 且 Data Size >=MSS；條件二：等待時間或是逾時 200ms，這兩個條件有一個滿足，才會發資料，否則就是在積累資料。Nagle 演算法預設是打開的，所以對於一些需要小包場景的程式 —— 比如像 telnet 或 ssh 這樣的互動性程式，需要關閉這個演算法。可以在 Socket 設定 TCP\_NODELAY 選項來關閉這個演算法。

#### 擁塞控制

擁塞控制是傳送方根據網路的承載情況控制分組的傳送量，以取得高效能又能避免擁塞崩潰（congestion collapse，網路效能下降幾個數量級）。這在網路流之間產生近似最大最小公平分配。

傳送方與接收方根據確認包或者包遺失的情況，以及定時器，估計網路擁塞情況，從而修改資料流的行為，這稱為擁塞控制或網路擁塞避免。

TCP 的現代實現包含四種相互影響的擁塞控制演算法：慢開始、擁塞避免、快速重傳、快速恢復。

此外，傳送方採取「逾時重傳」（retransmission timeout，RTO），這是估計出來回通訊延遲 (RTT) 以及 RTT 的方差。

RFC793 中定義的計算 SRTT 的經典演算法：指數加權移動平均（Exponential weighted moving average）

1. 先採樣 RTT，記下最近好幾次的 RTT 值。
2. 做平滑計算 SRTT 公式為： $SRTT=(\alpha *SRTT)+((1-\alpha )*RTT)$ ，其中 α 取值在 0.8 到 0.9 之間
3. 計算 RTO，公式： $RTO=min(UBOUND,max(LBOUND,(\beta *SRTT))$ ，其中 UBOUND 是最大的 timeout 時間上限值，LBOUND 是最小的 timeout 時間下限值，β 值一般在 1.3 到 2.0 之間。

1987 年，出現計算 RTT 的 Karn 演算法或 TCP 時間戳（RFC 1323），最大特點是 —— 忽略重傳，不把重傳的 RTT 做採樣。但是，如果在某一時間，網路閃動，突然變慢了，產生了比較大的延時，這個延時導致要重傳所有的包（因為之前的 RTO 很小），於是，因為重傳的不算，所以，RTO 就不會被更新，這是一個災難。為此，Karn 演算法一發生重傳，就對現有的 RTO 值翻倍。這就是的 Exponential backoff。

1988 年，在 RFC 6298 中給出范・雅各布森演算法取平均以獲得平滑往返時延（Smoothed Round Trip Time，SRTT），作為最終的 RTT 估計值。這個演算法在被用在今天的 TCP 協定中：

 $SRTT=SRTT+\alpha *(RTT-SRTT)$
 $DevRTT=(1-\beta )*DevRTT+\beta *\left\vert RTT-SRTT\right\vert $
 $RTO=\mu *SRTT+\partial *DevRTT$

其中：DevRTT 是 Deviation RTT。在 Linux 下，α = 0.125，β = 0.25， μ = 1，∂= 4

目前有很多 TCP 擁塞控制演算法在研究中。

### 最大分段大小

最大分段大小 (MSS) 是在單個分段中 TCP 願意接受的資料的位元組數最大值。MSS 應當足夠小以避免 IP 分片，它會導致丟包或過多的重傳。在 TCP 連接建立時，雙端在 SYN 報文中用 MSS 選項宣布各自的 MSS，這是從雙端各自直接相連的資料鏈路層的最大傳輸單元（MTU）的尺寸減去固定的 IP 首部和 TCP 首部長度。乙太網路 MTU 為 1500 位元組， MSS 值可達 1460 位元組。使用 IEEE 802.3 的 MTU 為 1492 位元組，MSS 可達 1452 位元組。如果目的 IP 位址為「非原生的」，MSS 通常的預設值為 536（這個預設值允許 20 位元組的 IP 首部和 20 位元組的 TCP 首部以適合 576 位元組 IP 資料報）。此外，傳送方可用傳輸路徑 MTU 發現（RFC 1191）推導出從傳送方到接收方的網路路徑上的最小 MTU，以此動態調整 MSS 以避免網路 IP 分片。

MSS 發布也被稱作「MSS 協商」（MSS negotiation）。嚴格講，這並非是協商出來一個統一的 MSS 值，TCP 允許連接兩端使用各自不同的 MSS 值。 例如，這會發生在參與 TCP 連接的一台裝置使用非常少的記憶體處理到來的 TCP 分組。

### 選擇確認

最初採取累計確認的 TCP 協定在丟包時效率很低。例如，假設通過 10 個分組發出了 1 萬個位元組的資料。如果第一個分組遺失，在純粹的累計確認協定下，接收方不能說它成功收到了 1,000 到 9,999 位元組，但未收到包含 0 到 999 位元組的第一個分組。因而，傳送方可能必須重傳所有 1 萬個位元組。

為此，TCP 採取了「選擇確認」（selective acknowledgment，SACK）選項。RFC 2018 對此定義為**允許接收方確認它成功收到的分組的不連續的塊**，以及基礎 TCP 確認的成功收到最後連續位元組序號。這種確認可以指出 _SACK block_，包含了已經成功收到的連續範圍的開始與結束位元組序號。在上述例子中，接收方可以發出 SACK 指出序號 1000 到 9999，傳送方因此知道只需重發第一個分組（位元組 0 到 999）。

TCP 傳送方會把亂序收包當作丟包，因此會重傳亂序收到的包，導致連接的效能下降。重複 SACK 選項（duplicate-SACK option）是定義在 RFC 2883 中的 SACK 的一項擴充，可解決這一問題。接收方發出 D-ACK 指出沒有丟包，接收方恢復到高傳輸率。D-SACK 使用了 SACK 的第一個段來做標誌，

* 如果 SACK 的第一個段的範圍被 ACK 所覆蓋，那麼就是 D-SACK;
* 如果 SACK 的第一個段的範圍被 SACK 的第二個段覆蓋，那麼就是 D-SACK

D-SACK 旨在告訴傳送端：收到了重複的資料，封包沒有丟，丟的是 ACK 包；或者「Fast Retransmit 演算法」觸發的重傳不是因為發出去的包丟了，也不是因為回應的 ACK 包丟了，而是因為網路延時導致的 reordering。

SACK 選項並不是強制的。僅當雙端都支援時才會被使用。TCP 連接建立時會在 TCP 頭中協商 SACK 細節。在 Linux 下，可以通過 tcp\_sack 參數打開 SACK 功能（Linux 2.4 後預設打開）。Linux 下的 tcp\_dsack 參數用於開啟 D-SACK 功能（Linux 2.4 後預設打開）。選擇確認也用於流控制傳輸協定 (SCTP).

### TCP 窗口縮放選項

TCP 窗口尺寸域控制封包在 2 至 65,535 位元組。RFC 1323 定義的 TCP 窗口縮放選項用於把最大窗口尺寸從 65,535 位元組擴大至 1G 位元組。擴大窗口尺寸是 TCP 最佳化的需要。

窗口縮放選項盡在 TCP 三次握手時雙端在 SYN 包中獨立指出這個方向的縮放係數。該值是 16 位元窗口尺寸的向左位移數，從 0 (表示不位移) 至 14。

某些路由器或分組防火牆會重寫窗口縮放選項，這可能導致不穩定的網路傳輸。

### TCP 時間戳

RFC 1323 定義了 TCP 時間戳，並不對應於系統時鐘，使用隨機值初始化。許多作業系統每毫秒增加一次時間戳；但 RFC 只規定 tick 應當成比例。

有兩個時間戳域:

　
: 4 位元組的傳送時間戳值

　
: 4 位元組的回應回覆時間戳值（最近收到資料的時間戳）

TCP 時間戳用於「防止序列號迴繞演算法」（Protection Against Wrapped Sequence numbers，PAWS），細節見 RFC 1323。PAWS 用於接收窗口跨序號迴繞邊界。這種情形下一個包可能會重傳以回答問題：「是否是第一個還是第二個 4 GB 的序號？」時間戳可以打破這一問題。

另外，Eifel 檢測演算法（ RFC 3522 ）使用 TCP 時間戳確定如果重傳發生是因為丟包還是簡單亂序。

最近統計表明時間戳的採用率停滯在～40%，這歸因於 Windows 伺服器從 Windows Server 2008 起降低了支援。.

### 帶外資料

帶外資料（OOB）是指對緊急資料，中斷或放棄排隊中的資料流；接收方應立即處理緊急資料。完成後，TCP 通知應用程式恢復流佇列的正常處理。

OOB 並不影響網路，「緊急」僅影響遠端端的處理。這一協定很少被實現。

### 強制資料遞交

正常情況下，TCP 等待 200 ms 以準備一個完整分組發出（納格演算法試圖把小的資訊組裝為單一的包）。這產生了小的、但潛在很嚴重的延遲並在傳遞一個檔案時不斷重複延遲。例如，典型傳送塊是 4 KB，典型的 MSS 是 1460 位元組，在 10 Mbit/s 乙太網路上發出兩個包，每個耗時約～1.2 ms，隨後是剩餘 1176 個位元組的包，之後是 197 ms 停頓因為 TCP 等待裝滿緩衝區。

對於 telnet，每次使用者擊鍵的回應，如果有 200 ms 將會非常煩人。

socket 選項 `TCP_NODELAY` 能放棄預設的 200 ms 傳送延遲。應用程式使用這個 socket 選項強制發出資料。

RFC 定義了 `PSH` 能立即發出位元。Berkeley 通訊端不能控制或指出這種情形，只能由協定棧控制。

### 終結通路

連接終止使用了四路握手過程（或稱四次握手，four-way handshake），在這個過程中連接的每一側都獨立地被終止。當一個端點要停止它這一側的連接，就向對側傳送 FIN，對側回覆 ACK 表示確認。因此，拆掉一側的連接過程需要一對 FIN 和 ACK，分別由兩側端點發出。

首先發出 FIN 的一側，如果給對側的 FIN 回應了 ACK，那麼就會逾時等待 2\*MSL 時間，然後關閉連接。在這段逾時等待時間內，原生的埠不能被新連接使用；避免延時的包的到達與隨後的新連接相混淆。RFC793 定義了 MSL 為 2 分鐘，Linux 設定成了 30s。參數 tcp\_max\_tw\_buckets 控制並行的 TIME\_WAIT 的數量，預設值是 180000，如果超限，那麼，系統會把多的 TIME\_WAIT 狀態的連接給 destory 掉，然後在紀錄檔里打一個警告（如：time wait bucket table overflow）

連接可以工作在 TCP 半開狀態。即一側關閉了連接，不再傳送資料；但另一側沒有關閉連接，仍可以傳送資料。已關閉的一側仍然應接收資料，直至對側也關閉了連接。

也可以通過測三路握手關閉連接。主機 A 發出 FIN，主機 B 回覆 FIN & ACK，然後主機 A 回覆 ACK.

一些主機（如 Linux 或 HP-UX）的 TCP 棧能實現半雙工關閉序列。這種主機如果主動關閉一個連接但還沒有讀完從這個連接已經收到的資料，該主機傳送 RST 代替 FIN。這使得一個 TCP 應用程式能確認遠端應用程式已經讀了所有已傳送資料，並等待遠端側發出的 FIN。但是遠端的 TCP 棧不能區分 _Connection Aborting RST_ 與 _Data Loss RST_，兩種原因都會導致遠端的 TCP 棧失去所有的收到資料。

一些應用協定使用 TCP open/close handshaking，因為應用協定的 TCP open/close handshaking 可以發現主動關閉的 RST 問題。例如：

```
s = connect(remote);
send(s, data);
close(s);
```

TCP/IP 棧採用上述方法不能保證所有資料到達對側，如果未讀資料已經到達對側。

### 狀態編碼

下表為 TCP 狀態碼列表，以 **S** 指代伺服器，**C** 指代客戶端，**S&C** 表示兩者，**S/C** 表示兩者之一：

**LISTEN S**
: 伺服器等待從任意遠端 TCP 埠的連接請求。偵聽狀態。

**SYN-SENT C**
: 客戶在傳送連接請求後等待匹配的連接請求。通過 connect () 函式向伺服器發出一個同步（SYNC）訊號後進入此狀態。

**SYN-RECEIVED S**
: 伺服器已經收到並行送同步（SYNC）訊號之後等待確認（ACK）請求。

**ESTABLISHED S&C**
: 伺服器與客戶的連接已經打開，收到的資料可以傳送給使用者。資料傳輸步驟的正常情況。此時連接兩端是平等的。這稱作全連接。

**FIN-WAIT-1 S&C**
: （伺服器或客戶）主動關閉端呼叫 close（）函式發出 FIN 請求包，表示本方的資料傳送全部結束，等待 TCP 連接另一端的 ACK 確認包或 FIN&ACK 請求包。

**FIN-WAIT-2 S&C**
: 主動關閉端在 FIN-WAIT-1 狀態下收到 ACK 確認包，進入等待遠端 TCP 的連接終止請求的半關閉狀態。這時可以接收資料，但不再傳送資料。

**CLOSE-WAIT S&C**
: 被動關閉端接到 FIN 後，就發出 ACK 以回應 FIN 請求，並進入等待本地使用者的連接終止請求的半關閉狀態。這時可以傳送資料，但不再接收資料。

**CLOSING S&C**
: 在發出 FIN 後，又收到對方發來的 FIN 後，進入等待對方對己方的連接終止（FIN）的確認（ACK）的狀態。少見。

**LAST-ACK S&C**
: 被動關閉端全部資料傳送完成之後，向主動關閉端傳送 FIN，進入等待確認包的狀態。

**TIME-WAIT S/C**
: 主動關閉端接收到 FIN 後，就傳送 ACK 包，等待足夠時間以確保被動關閉端收到了終止請求的確認包。（按照 RFC 793，一個連接可以在 TIME-WAIT 保證最大四分鐘，即最大分段壽命（maximum segment lifetime）的 2 倍）

**CLOSED S&C**
: 完全沒有連接。

## 埠

TCP 使用了通信埠（Port number）的概念來標識傳送方和接收方的應用層。對每個 TCP 連接的一端都有一個相關的 16 位元的無符號埠號分配給它們。埠被分為三類：眾所周知的、註冊的和動態 / 私有的。眾所周知的埠號是由網際網路賦號管理局（IANA）來分配的，並且通常被用於系統一級或根行程。眾所周知的應用程式作為伺服器程式來執行，並被動地偵聽經常使用這些埠的連接。例如：FTP、TELNET、SMTP、HTTP、IMAP、POP3 等。註冊的埠號通常被用來作為終端使用者連接伺服器時短暫地使用的源埠號，但它們也可以用來標識已被第三方註冊了的、被命名的服務。動態 / 私有的埠號在任何特定的 TCP 連接外不具有任何意義。可能的、被正式承認的埠號有 65535 個。

## 封包結構

* 來源連接埠（16 位元長）－辨識傳送連接埠
* 目的連接埠（16 位元長）－辨識接收連接埠
* 序列號（seq，32 位元長）
  * 如果含有同步化旗標（SYN），則此為最初的序列號；第一個資料位元的序列碼為本序列號加一。
  * 如果沒有同步化旗標（SYN），則此為第一個資料位元的序列碼。
* 確認號（ack，32 位元長）— 期望收到的資料的開始序列號。也即已經收到的資料的位元組長度加 1。
* 資料偏移（4 位元長）— 以 4 位元組為單位計算出的資料段開始位址的偏移值。
* 保留（3 位元長）— 須置 0
* 標誌符（9 位元長）
  * NS—ECN-nonce。ECN 顯式擁塞通知（Explicit Congestion Notification）是對 TCP 的擴充，定義於 RFC 3540 （2003）。ECN 允許擁塞控制的端對端通知而避免丟包。ECN 為一項可選功能，如果底層網路設施支援，則可能被啟用 ECN 的兩個端點使用。在 ECN 成功協商的情況下，ECN 感知路由器可以在 IP 頭中設定一個標記來代替丟棄封包，以標明阻塞即將發生。封包的接收端回應傳送端的表示，降低其傳輸速率，就如同在往常中檢測到包遺失那樣。
  * CWR—Congestion Window Reduced，定義於 RFC 3168（2001）。
  * ECE—ECN-Echo 有兩種意思，取決於 SYN 標誌的值，定義於 RFC 3168（2001）。
  * URG— 為 1 表示高優先級封包，緊急指標欄位有效。
  * ACK— 為 1 表示確認號欄位有效
  * PSH— 為 1 表示是帶有 PUSH 標誌的資料，指示接收方應該儘快將這個報文段交給應用層而不用等待緩衝區裝滿。
  * RST— 為 1 表示出現嚴重差錯。可能需要重新建立 TCP 連接。還可以用於拒絕非法的報文段和拒絕連接請求。
  * SYN— 為 1 表示這是連接請求或是連接接受請求，用於建立連接和使順序號同步
  * FIN— 為 1 表示傳送方沒有資料要傳輸了，要求釋放連接。
* 窗口（WIN，16 位元長）— 表示從確認號開始，本報文的傳送方可以接收的位元組數，即接收窗口大小。用於流量控制。
* 校驗和（Checksum，16 位元長）— 對整個的 TCP 報文段，包括 TCP 頭部和 TCP 資料，以 16 位元字進行計算所得。這是一個強制性的欄位。
* 緊急指標（16 位元長）— 本報文段中的緊急資料的最後一個位元組的序號。
* 選項欄位 — 最多 40 位元組。每個選項的開始是 1 位元組的 kind 欄位，說明選項的類型。
  * 0：選項表結束（1 位元組）
  * 1：無操作（1 位元組）用於選項欄位之間的字邊界對齊。
  * 2：最大報文段長度（4 位元組，Maximum Segment Size，MSS）通常在建立連接而設定 SYN 標誌的封包中指明這個選項，指明本端所能接收的最大長度的報文段。通常將 MSS 設定為（MTU-40）位元組，攜帶 TCP 報文段的 IP 資料報的長度就不會超過 MTU（MTU 最大長度為 1518 位元組，最短為 64 位元組），從而避免本機發生 IP 分片。只能出現在同步報文段中，否則將被忽略。
  * 3：窗口擴大因子（3 位元組，wscale），取值 0-14。用來把 TCP 的窗口的值左移的位數，使窗口值乘倍。只能出現在同步報文段中，否則將被忽略。這是因為現在的 TCP 接收資料緩衝區（接收窗口）的長度通常大於 65535 位元組。
  * 4：sackOK— 傳送端支援並同意使用 SACK 選項。
  * 5：SACK 實際工作的選項。
  * 8：時間戳（10 位元組，TCP Timestamps Option，TSopt）
    * 傳送端的時間戳（Timestamp Value field，TSval，4 位元組）
    * 時間戳回顯應答（Timestamp Echo Reply field，TSecr，4 位元組）
  * 19：MD5 摘要，將 TCP 偽首部、校驗和為 0 的 TCP 首部、TCP 資料段、通信雙方約定的金鑰（可選）計算出 MD5 摘要值並附加到該選項中，作為類似對 TCP 報文的簽章。通過 RFC 2385 引入，主要用於增強 BGP 通信的安全性。
  * 29：安全摘要，通過 RFC 5925 引入，將「MD5 摘要」的雜湊方法更換為 SHA 雜湊演算法。

## 發展過程

TCP 是一個複雜的但同時又是在發展之中的協定。儘管許多重要的改進被提出和實施，發表於 1981 年的 RFC793 中說明的 TCP（TCP-Tahoe）的許多基本操作還是未作多大改動。RFC1122：《網際網路對主機的要求》闡明了許多 TCP 協定的實現要求。RFC2581：《TCP 的擁塞控制》是一篇近年來關於 TCP 的很重要的 RFC，描述了更新後的避免過度擁塞的演算法。寫於 2001 年的 RFC3168 描述了對明顯擁塞的報告，這是一種擁塞避免的號誌機制。在 21 世紀早期，在所有網際網路的封包中，通常有大約 95% 的包使用了 TCP 協定。常見的使用 TCP 的應用層有 HTTP/HTTPS（全球資訊網協定），SMTP/POP3/IMAP（電子郵件協定）以及 FTP（檔案傳輸協定）。這些協定在今天被廣泛地使用，這證明了它們的原作者的創造是卓越的。

最近，一個新協定已經被加州理工學院的科研人員開發出來，命名為 FAST TCP（基於快速活動佇列管理的規模可變的傳輸控制協定）。它使用排隊延遲作為擁塞控制訊號；但是因為端到端的延遲通常不僅僅包括排隊延遲，所以 FAST TCP（或更一般地，所有基於排隊延遲的演算法）在實際網際網路中的能否工作仍然是一個沒有解決的問題。

## 應用

TCP 並不是對所有的應用都適合，一些新的帶有一些內在的脆弱性的運輸層協定也被設計出來。比如，即時應用並不需要甚至無法忍受 TCP 的可靠傳輸機制。在這種類型的應用中，通常允許一些丟包、出錯或擁塞，而不是去校正它們。例如通常不使用 TCP 的應用有：串流媒體、網路遊戲、IP 電話（VoIP）等等。任何不是很需要可靠性或者是想將功能減到最少的應用可以避免使用 TCP。在很多情況下，當只需要多路復用應用服務時，使用者資料報協定（UDP）可以代替 TCP 為應用提供服務。

除外，由於 TCP 的實現是由作業系統提供，而 TCP 的悠久歷史、系統級別的組態機制，一些特性在特定的網路環境下會成為一種累贅而且無法最佳化，所以也有一些通過在 UDP 上重新實現使用者層級的類似 TCP 的連接導向的、可靠的、基於位元組流的類傳輸層協定，來代替 TCP，例如 QUIC。

## 參見

* TCP 校驗和解除安裝
* TCP/UDP 埠列表
* SCTP
* 連接重設
* 擁塞控制

## 參考資料

## 外部連結

* RFC 793（1981 年）
* RFC 1323（1992 年）
* RFC 3168（2001 年）
* RFC 3540（2003 年）