TCP 全稱是什麼?傳輸控制協議的特點、首部與工作過程
TCP 的全稱是 Transmission Control Protocol,中文叫傳輸控制協議,是 TCP/IP 協議簇裡負責可靠傳輸的傳輸層協議。它先建立連線,再用序號、確認和重傳保證資料不丟、不亂、不重,並用流量控制和擁塞控制調節傳送速度;網頁、SSH、遠端桌面和資料庫都跑在它上面。
TCP 全稱是 Transmission Control Protocol,中文譯為傳輸控制協議。它是 TCP/IP 協議簇中工作在傳輸層的協議,封裝在 IP 包裡,IPv4 頭部用協議號 6 標識它。TCP 最早在 1981 年由 RFC 793 標準化,2022 年釋出的 RFC 9293 把此後幾十年的修訂合併成新的標準文字,取代了 RFC 793。用一句話回答“TCP 是什麼協議”:它在兩台主機的兩個程式之間,提供面向連線、可靠、按序的位元組流傳輸。伺服器上的網站(HTTP、HTTPS)、SSH、遠端桌面、MySQL 這類資料庫,都依賴 TCP 把資料完整地送到對端。下面依次講它的特點、首部欄位、一次連線的完整過程、流量控制與擁塞控制,以及伺服器維運中最常碰到的 TCP 報錯。
TCP 是什麼協議:在 IP 之上補上“可靠”
IP 只負責“盡力而為”地把包送到目標主機:路上可能丟包,可能因為走了不同路徑而亂序,可能因為重發而重複,也可能在傳輸中出錯。應用程式如果直接面對這些問題,每個程式都得自己寫一遍重傳和排序。TCP 的作用就是在作業系統核心裡統一處理掉這些問題,讓程式只需要“寫進去、讀出來”。
| IP 層可能發生的情況 | TCP 用什麼機制解決 |
|---|---|
| 包丟了 | 接收方對收到的資料發確認,傳送方超時沒收到確認就重傳 |
| 包亂序到達 | 每個位元組都有序號,接收方按序號重新排好再交給程式 |
| 包重複到達 | 按序號識別出已經收過的資料,直接丟棄 |
| 內容在傳輸中出錯 | 校驗和不對的報文段直接丟棄,靠重傳補上 |
| 接收方處理不過來 | 流量控制:接收方在每個報文裡通告自己還能收多少 |
| 網路本身擁堵 | 擁塞控制:傳送方根據丟包和延遲主動降速 |
有兩件事 TCP 不管:一是不加密,資料線上路上是明文,HTTPS、SSH 的加密由 TLS 或 SSH 協議自己完成;二是不保證時效,它保證資料最終完整到達,但丟包重傳時,後面的資料要等前面的補齊才能交給程式,延遲會上升。
TCP 只存在於通訊的兩端。路由器和二層交換器轉發時只看 IP 和 MAC,不維護 TCP 狀態;防火牆、負載均衡、NAT 裝置會讀取 TCP 頭部,按連線狀態做判斷,這也是它們能攔截或打斷 TCP 連線的原因。
TCP 協議的特點:五個關鍵詞分別指什麼
| 特點 | 具體含義 | 帶來的代價或注意點 |
|---|---|---|
| 面向連線 | 傳資料之前先握手,雙方交換初始序號,告知各自的最大報文段長度(MSS),並協商視窗縮放、SACK 等選項;連線由源 IP、源連接埠、目的 IP、目的連接埠四元組唯一確定 | 多一個往返才能開始傳資料;每條連線在兩端都佔用記憶體和狀態 |
| 可靠傳輸 | 序號按位元組編號,確認號表示“下一個期望收到的位元組”;超時未確認就重傳,收到 3 個重複確認會提前重傳(快速重傳) | 丟包時延遲上升;Linux 的重傳超時最小約 200 毫秒 |
| 位元組流 | 程式寫入的資料被看作連續的位元組,按 MSS 切分傳送,不保留每次寫入的邊界 | 應用層要自己劃分訊息,也就是常說的“粘包”問題 |
| 全雙工 | 兩個方向各有獨立的序號和視窗,可以同時收發;一方關閉傳送後,仍可繼續接收(半關閉) | 關閉連線時兩個方向要分別關閉 |
| 流量與擁塞控制 | 傳送量同時受接收方視窗和傳送方估算的擁塞視窗限制 | 長距離、有丟包的線路上,單條連線往往跑不滿頻寬 |
另外,TCP 只支援一對一通訊。廣播、組播這類一對多的場景用不了 TCP,只能用 UDP。
“面向連線”中的四元組值得多說一句:伺服器上的 Nginx 只監聽一個 443 連接埠,卻能同時服務幾萬個客戶端,因為每個客戶端的 IP 和源連接埠不同,四元組各不相同,核心據此把報文分發到各自的連線上。
TCP 首部格式:20 位元組固定欄位
TCP 首部固定部分 20 位元組,後面可以跟最多 40 位元組的選項,所以首部長度在 20 到 60 位元組之間。按每行 4 位元組排列:
位元組 0–3 | 源連接埠(16 位) | 目的連接埠(16 位) |
位元組 4–7 | 序號(32 位) |
位元組 8–11 | 確認號(32 位) |
位元組 12–15 | 首部長度(4 位)| 保留(4 位)| 標誌位(8 位)| 視窗(16 位) |
位元組 16–19 | 校驗和(16 位) | 緊急指標(16 位) |
位元組 20 起 | 選項(0–40 位元組,長度為 4 的倍數) |
| 欄位 | 長度 | 作用 | 維運中在哪裡見到 |
|---|---|---|---|
| 源連接埠 | 16 位 | 傳送方的連接埠,客戶端一般由系統從臨時連接埠範圍裡分配 | Linux 預設臨時連接埠範圍 32768–60999,sysctl net.ipv4.ip_local_port_range 檢視 |
| 目的連接埠 | 16 位 | 接收方程式監聽的連接埠 | 防火牆規則裡的 --dport 443 |
| 序號 | 32 位 | 本報文段第一個資料位元組的編號;SYN 報文裡是隨機生成的初始序號 | tcpdump 輸出的 seq |
| 確認號 | 32 位 | 期望收到對方的下一個位元組編號,ACK 標誌置位時有效 | tcpdump 輸出的 ack |
| 首部長度 | 4 位 | 以 4 位元組為單位,取值 5–15,對應 20–60 位元組 | 抓包工具裡的 Header Length |
| 保留 | 4 位 | 置 0,留作擴充套件 | — |
| 標誌位 | 8 位 | CWR、ECE、URG、ACK、PSH、RST、SYN、FIN | tcpdump 的 Flags [S.] |
| 視窗 | 16 位 | 接收方還能接收多少位元組,需乘以握手時協商的縮放因子 | win 值長時間為 0 表示接收方處理不過來 |
| 校驗和 | 16 位 | 覆蓋偽首部(源 IP、目的 IP、協議號、長度)、首部和資料 | 本機發出的包在抓包裡顯示校驗和錯誤,多半是網路卡校驗和解除安裝,不是故障 |
| 緊急指標 | 16 位 | URG 置位時指明緊急資料的位置,現在幾乎不用 | — |
八個標誌位裡,日常排障用得最多的是這幾個,括號裡是 tcpdump 的顯示符號:SYN(S)發起連線、ACK(.)確認、FIN(F)正常關閉、RST(R)強制復位、PSH(P)提示對方儘快把資料交給程式。ECE(E)和 CWR(W)用於顯式擁塞通知(ECN),URG(U)用於緊急資料。
常見選項都只在握手階段協商一次:MSS 告訴對方自己能接收的最大資料段,乙太網 MTU 1500 時通常是 1460 位元組;視窗縮放把 16 位視窗左移最多 14 位,使視窗上限從 65535 位元組擴大到約 1 GB;SACK 允許接收方告訴傳送方“哪幾段已經收到”,丟包時只重傳缺的部分;時間戳用於更準確地測量往返時間,並防止序號迴繞帶來的混淆。
一次 TCP 連線的完整過程:從握手到揮手
在伺服器 198.51.100.23 上執行 curl http://203.0.113.10/,另開一個視窗抓包:
tcpdump -nn -i eth0 host 203.0.113.10 and tcp port 80
整個連線大致由下面這些報文組成。為了便於閱讀,雙方的初始序號都記為 0、後面用相對值表示(tcpdump 在握手報文裡顯示真實的隨機序號,之後才換算成相對值),資料大小為範例:
| 序 | 方向 | 標誌 | 序號與確認號 | 內容 | 階段 |
|---|---|---|---|---|---|
| 1 | 客戶端 → 伺服器 | S | seq 0 | 攜帶 MSS 1460、SACK、視窗縮放等選項 | 握手 |
| 2 | 伺服器 → 客戶端 | S. | seq 0,ack 1 | 伺服器的初始序號和選項 | 握手 |
| 3 | 客戶端 → 伺服器 | . | ack 1 | 連線建立 | 握手 |
| 4 | 客戶端 → 伺服器 | P. | seq 1:79 | 78 位元組的 HTTP 請求 | 傳資料 |
| 5 | 伺服器 → 客戶端 | . | ack 79 | 確認收到請求 | 傳資料 |
| 6 | 伺服器 → 客戶端 | . | seq 1:1449 | 第一段回應,1448 位元組 | 傳資料 |
| 7 | 伺服器 → 客戶端 | P. | seq 1449:2897 | 第二段回應,1448 位元組 | 傳資料 |
| 8 | 客戶端 → 伺服器 | . | ack 2897 | 一次確認兩段資料 | 傳資料 |
| 9 | 客戶端 → 伺服器 | F. | seq 79 | curl 收完回應,主動關閉 | 揮手 |
| 10 | 伺服器 → 客戶端 | F. | ack 80 | 確認對方的 FIN,同時發出自己的 FIN | 揮手 |
| 11 | 客戶端 → 伺服器 | . | ack 2898 | 確認伺服器的 FIN | 揮手 |
從這張表能讀出 TCP 的幾個關鍵行為:
- 握手只做協商,不傳業務資料。前三個報文的長度都是 0,真正的請求從第 4 個報文才開始。SYN 和 FIN 各佔用一個序號,所以第 3 個報文確認的是 1,第 10 個報文確認的是 80。
- 確認是累計的。第 8 個報文的 ack 2897 一次確認了第 6、7 兩段,不必逐段確認;Linux 還會稍微延遲傳送確認,等著和要發的資料合併。
- 資料段大小是 1448 而不是 1460。雙方啟用了時間戳選項,每個報文段要拿出 12 位元組放選項,MSS 1460 減去 12 就是 1448。
- 揮手常常只有三個包。理論上關閉連線要四個報文,兩個方向各一對 FIN 和 ACK;伺服器收到 FIN 時如果程式也馬上關閉,核心就把 ACK 和自己的 FIN 合併成一個報文發出。
- 先關閉的一方進入 TIME_WAIT。這裡是 curl 先發 FIN,所以 TIME_WAIT 留在客戶端,Linux 上持續 60 秒,用
ss -tan state time-wait能看到。
流量控制和擁塞控制有什麼區別
兩者都是“控制傳送速度”,但保護的物件不同:
| 對比項 | 流量控制 | 擁塞控制 |
|---|---|---|
| 保護誰 | 接收方,防止它的緩衝區被灌滿 | 網路,防止路徑上的鏈路和裝置過載 |
| 依據 | 接收方在每個報文的視窗欄位裡通告的接收視窗(rwnd) | 傳送方自己估算的擁塞視窗(cwnd),不寫在報文裡 |
| 訊號來源 | 接收緩衝區還剩多少空間 | 丟包、往返時間變長、ECN 標記 |
| 調節方式 | 接收方調大或調小通告視窗,處理不過來時通告 0 | 慢啟動、擁塞避免、遇到丟包時降低視窗 |
| 常見問題 | 視窗太小,長距離傳輸跑不滿頻寬 | 線路有丟包,視窗一再被壓低 |
傳送方任何時刻在途(已發出但未確認)的資料量,不超過 rwnd 和 cwnd 中較小的那個。由此有一個很實用的估算:單條連線的速度上限約等於視窗除以往返時間。
- 不啟用視窗縮放時,視窗最大 65535 位元組。往返時間 150 毫秒的跨境線路上,上限約為 65535 × 8 ÷ 0.15 ≈ 3.5 Mbit/s,頻寬再大也用不上。
- 反過來,要在往返時間 150 毫秒的線路上跑滿 100 Mbit/s,視窗至少要 100,000,000 × 0.15 ÷ 8 ≈ 1.9 MB。
擁塞視窗從小開始增長。Linux 的初始擁塞視窗是 10 個報文段,約 14 KB;慢啟動階段每經過一個往返大約翻一倍。假設往返時間 30 毫秒、頻寬 100 Mbit/s,需要的視窗約 375 KB,即 259 個 1448 位元組的報文段,從 10 翻倍到這個量級要 5 個往返左右。這解釋了一個常見現象:下載幾十 KB 的小檔案時,連線還沒走完慢啟動就結束了,測出的速度遠低於頻寬,這不是線路問題。
遇到丟包時,擁塞控制會把視窗壓低:經典的 Reno 演算法減半,Linux 預設的 CUBIC 降到原來的約 70%,之後再逐步恢復。BBR 則主要依據測得的頻寬和往返時間來定速,不把丟包作為主要訊號。在伺服器上檢視和觀察:
sysctl net.ipv4.tcp_congestion_control # 當前演算法,多數發行版預設 cubic
sysctl net.ipv4.tcp_available_congestion_control # 核心已載入、可以切換的演算法
ss -tin state established '( sport = :443 )' # 檢視本機 443 連接埠上每條連線的內部引數
ss 的 -i 輸出裡,cwnd:10 是當前擁塞視窗(單位是報文段),rtt:3.5/1.2 是平滑往返時間和波動(毫秒),retrans:0/3 是當前未恢復的和累計的重傳次數,wscale:7,7 是雙方的視窗縮放因子。某個客戶反映“下載慢”時,看他那條連線的 rtt 和 retrans,就能初步判斷是距離遠、線路丟包,還是伺服器自身的問題。
伺服器上哪些服務用 TCP,連線報錯對應什麼
| 服務 | 預設連接埠 | 為什麼用 TCP |
|---|---|---|
| HTTP / HTTPS | 80 / 443 | 網頁、介面的內容一個位元組都不能錯;HTTP/3 改用 UDP 443 上的 QUIC 是例外 |
| SSH、SFTP | 22 | 命令和檔案必須完整、按序到達 |
| 遠端桌面 RDP | 3389 | 認證和會話建立必須走 TCP,畫面傳輸可以額外用 UDP |
| MySQL / PostgreSQL / Redis | 3306 / 5432 / 6379 | 查詢和結果不能丟失或亂序 |
| SMTP、IMAP | 25、465、587 / 143、993 | 郵件投遞要求完整送達 |
| FTP | 21,另有資料連接埠 | 控制連線和資料連線都是 TCP |
| BGP | 179 | 路由協議借用 TCP 的可靠性,自己不再實現重傳 |
| DNS 區域傳送和大應答 | 53 | 普通查詢走 UDP,資料量大時改用 TCP |
正因為幾乎所有核心服務都依賴 TCP,伺服器上很多“連不上”“斷開了”的問題,本質是 TCP 層發生了某個具體事件。程式報出的錯誤資訊可以直接對應到報文:
| 程式報錯 | TCP 層發生了什麼 | 常見原因 |
|---|---|---|
| Connection refused | 發出 SYN 後立即收到 RST(或 ICMP 連接埠不可達) | 連接埠上沒有程式監聽;或防火牆用 REJECT 拒絕,回了 TCP 復位或 ICMP 連接埠不可達 |
| Connection timed out | SYN 重傳多次都沒有任何應答 | 安全組或防火牆靜默丟棄;主機不線上;回程路由有問題 |
| Connection reset by peer | 連線建立後收到 RST | 對端程式崩潰或強制關閉連線;中間裝置的會話已超時被刪除 |
| Broken pipe | 向已經被對端關閉或復位的連線寫資料 | 對端已經關閉;長連線空閒太久被中間裝置斷開 |
| No route to host | 收到 ICMP 主機不可達 | 路由缺失;或防火牆以 ICMP 方式拒絕,firewalld 預設的拒絕就是這種 |
最後一類問題在機房裡很常見:空閒連線被中間裝置悄悄斷開。狀態防火牆和 NAT 裝置會刪除一段時間沒有報文的連線記錄,而 Linux 的 TCP 保活(keepalive)預設要空閒 7200 秒才開始探測,遠長於很多裝置的超時時間。SSH 空閒一會兒就卡死、資料庫連線池裡的連線用的時候才發現已失效,都是這個原因。處理辦法是讓應用層更頻繁地發保活:SSH 客戶端在 ~/.ssh/config 裡設定 ServerAliveInterval 60,或在伺服器的 sshd_config 裡設定 ClientAliveInterval 60;資料庫連線池開啟空閒檢測。
在管理系統裡怎麼落地
伺服器的 SSH 和遠端桌面都建立在它自己的 TCP 協議棧上:網路卡配置寫錯、防火牆把 22 或 3389 擋住、系統卡死,TCP 連線就建立不起來,再多的重試也沒用。Toplink DCIM 的 IPMI 遠端管理 在瀏覽器裡同時提供 SSH、RDP 和 VNC 控制檯:前兩者走伺服器作業系統的通道,日常使用;VNC 經由 BMC 連線,不依賴伺服器系統裡的網路和 TCP 服務,系統層面的連線出問題時,仍然能看到畫面進去修復。網路裝置一側也離不開 TCP:交換器管理 預設透過 SSH 或 Telnet 下發連接埠、VLAN 等配置,這兩種都是 TCP 上的會話,每一條命令都要可靠送達;SNMP 只用於唯讀採集。
常見問題
TCP 連接埠和 UDP 連接埠是同一套嗎?
不是。TCP 和 UDP 各有一套 0–65535 的連接埠號,TCP 53 和 UDP 53 是兩個獨立的連接埠,同一個連接埠號可以分別被兩個程式佔用。防火牆和安全組放行時要按協議分別寫規則。
TCP 粘包是什麼意思?
TCP 是位元組流,不保留訊息邊界:程式連續寫入的兩條訊息,對端可能一次讀到,也可能一條訊息分兩次才讀完。這不是故障,而是應用層要自己定邊界,常用固定長度、長度字首或分隔符三種辦法。
TCP 一定比 UDP 慢嗎?
不一定。TCP 建連多一個往返,丟包時要等重傳;但在穩定的線路上傳大檔案,TCP 同樣能跑滿頻寬。即時音影片、遊戲這類寧可丟資料也不願等重傳的業務,才更適合 UDP。