傳輸控制協議

傳輸控制協議（Transmission Control Protocol，縮寫：TCP）是一種面向連接的、可靠的、基於字節流的傳輸層通信協議，由IETF的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指定。

三次握手協議的過程：

- 客戶端（通過執行connect函數）向伺服器端發送一個SYN包，請求一個主動打開。該包攜帶客戶端為這個連接請求而設定的隨機數A作為消息序列號。
- 伺服器端收到一個合法的SYN包後，把該包放入SYN隊列中；回送一個SYN/ACK。ACK的確認碼應為A+1，SYN/ACK包本身攜帶一個隨機產生的序號B。
- 客戶端收到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（可變化）和1460位元組數據的TCP報文段給接收方。接收方以一個沒有數據的TCP報文段來回復（只含報頭），用確認號1461來表示已完全收到並請求下一個報文段。
- 發送方然後發送第二個包含序列號為1461，長度為1460位元組的數據的TCP報文段給接收方。正常情況下，接收方以一個沒有數據的TCP報文段來回復，用確認號2921（1461+1460）來表示已完全收到並請求下一個報文段。發送接收這樣繼續下去。
- 然而當這些數據包都是相連的情況下，接收方沒有必要每一次都回應。比如，他收到第1到5條TCP報文段，只需回應第五條就行了。在例子中第3條TCP報文段被丟失了，所以儘管他收到了第4和5條，然而他只能回應第2條。
- 發送方在發送了第三條以後，沒能收到回應，因此當時鐘（timer）過時（expire）時，他重發第三條。（每次發送者發送一條TCP報文段後，都會再次啟動一次時鐘：RTT）。
- 這次第三條被成功接收，接收方可以直接確認第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）

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

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

1988年，在中給出范·雅各布森演算法取平均以獲得平滑往返時延（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發現推導出從發送方到接收方的網路路徑上的最小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個。

## 封包結構

偏移｜位元組｜0｜1｜2｜3

位元組｜位元｜0｜1｜2｜3｜4｜5｜6｜7｜8｜9｜10｜11｜12｜13｜14｜15｜16｜17｜18｜19｜20｜21｜22｜23｜24｜25｜26｜27｜28｜29｜30｜31

0｜0｜來源連接埠｜目的連接埠

4｜32｜序列號碼

8｜64｜確認號碼（當ACK設定）

12｜96｜資料偏移｜保留0 0 0｜NS｜CWR｜ECE｜URG｜ACK｜PSH｜RST｜SYN｜FIN｜窗口大小

16｜128｜校驗和｜緊急指標（當URG設定）

20...｜160...｜選項（如果資料偏移 > 5，需要在結尾添加0。）...

- 來源連接埠（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的悠久歷史、系統級別的配置機制，一些特性在特定的網路環境下會成為一種累贅而且無法優化，所以也有一些通過在UDP上重新實現使用者層級的類似TCP的面向連接的、可靠的、基於字節流的類傳輸層協議，來代替TCP，例如基於UDP的數據傳輸協議、QUIC、亞馬遜雲計算服務的Scalable Reliable Datagram (SRD)，以及史丹佛大學為解決隊頭阻塞提出的Homa。

## 應用

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