页面打开慢、接口超时、抓包里出现重传和重复确认时,问题往往不在 HTTP 语义本身,而在它下面的传输连接。TCP 常被概括成“可靠传输协议”,但真正决定工程判断的,是它如何通过连接、确认、重传、窗口和按序交付,把不可靠的网络路径变成可用的字节流。
TCP 的代价也在这里。它解决了开放互联网里的基本可靠传输问题,也把后来的 Web 性能、弱网体验和多路复用限制一起带到应用层之下。HTTP/2 为什么会被丢包拖住,QUIC 为什么要重做一层传输,答案都在 TCP 的行为模型里。
TCP 的核心,不是“保证数据一定到达”,而是在不可靠 IP 之上,用连接状态、确认重传、窗口控制和按序交付换出一条可持续传输的字节流
它为什么会出现
IP 解决的是“把一个个数据包尽力送到目标地址”,不是“替应用可靠地交付一段完整数据”。在更早的网络现实里,这会直接带来几类问题:
- 包可能丢
- 包可能乱序到达
- 包可能重复到达
- 发送方可能远快于接收方
- 整个网络可能因为过量发送而进入拥塞
如果每个应用都自己处理这些问题,代价很高,而且互操作性很差。TCP 的价值就在于把这些高频共性问题收束为一层通用机制,让上层应用拿到的是连续字节流,而不是一组容易失序的数据报。
它是谁在什么背景下做出来的
TCP 出自早期互联网协议体系,由 DARPA 资助推动,后来成为 TCP/IP 协议族的关键组成部分。它面对的不是今天单一厂商可控的数据中心,而是异构主机、长距离链路和不稳定公网。
这决定了 TCP 的设计重心:
- 默认底层网络并不可靠
- 默认通信双方能力和处理速度并不一致
- 默认连接可能持续一段时间,而不是只发一个数据报就结束
- 默认协议一旦大规模部署,就必须长期兼容
所以 TCP 不是一个只为“局域网内高速传文件”优化的协议,而是一种在开放互联网里尽量保守、尽量稳妥的通用传输层方案。
先理解它的主模型
TCP 可以先看成四个关键词:
- 面向连接
- 面向字节流
- 确认与重传
- 流量控制与拥塞控制
这四个点里,字节流最容易被低估。TCP 不知道 HTTP 请求、TLS 记录或者自定义协议里的一条消息从哪里开始、到哪里结束。对 TCP 来说,应用交给它的是一串字节;它负责的是尽量可靠、按序地把这串字节交给对端。
这既是它的优势,也是很多限制的来源。
一条典型链路
看一条典型的 TCP 连接建立和数据交换路径:
这条主链路背后,TCP 同时完成几件事:
- 确认双方都愿意建立连接
- 为双向传输各自建立序号空间
- 通过确认号告诉对方“哪些字节已经收到”
- 在丢包时触发重传
- 通过窗口和拥塞控制避免把接收端或网络本身压垮
表面上只是三次握手加一串 ACK,实际承载的是一套持续维护连接状态的机制。
为什么要三次握手
三次握手的作用,是让双方都确认两件事:
- 对方是否可达
- 双方的初始序号和连接状态是否已经建立
可以写成这样:
如果只有两次握手,服务端无法确认客户端是否收到了自己的 SYN+ACK,连接状态就可能悬空。第三次握手的作用,是把“双方都知道对方可收发”的状态闭合起来。
这也解释了为什么抓包里看到大量 SYN 发出却没拿到完整三次握手时,问题通常要先往网络可达性、防火墙、监听端口或者中间设备丢包上查,而不是先怀疑应用协议。
为什么 TCP 要按序交付
TCP 不只是可靠传输,还承诺按序交付。这一层承诺直接简化了应用实现:
- 应用不必自己重组乱序数据
- 上层协议可以按连续字节流解析
- 很多传统协议实现能保持简单
但代价也很大。只要某个前面的数据段没到,后面已经到了的数据也不能先交给应用。这就是 TCP 队头阻塞的根源。
这个设计在文件传输、数据库连接、早期 Web 里都很自然,因为一条连接承载的通常就是一段线性数据流。到了后来 HTTP/2 这种“一条连接里并发很多请求”的场景,问题就开始放大了。
为什么确认和重传会带来性能代价
TCP 靠确认和重传换可靠性,但它们不是免费的。
当一个数据段丢失时,发送方通常会通过下面两类信号发现:
- 超时还没收到确认
- 连续收到重复确认,推测中间有段丢了
随后它会重传缺失的数据。这个机制保证了可靠性,但也带来连锁反应:
- 等待确认会增加时延
- 重传会占用带宽
- 按序交付会让后续已到达数据暂时不能上交
- 拥塞控制可能进一步收紧发送速度
所以“TCP 保证可靠”这句话的另一面是:它用更多等待、更多状态和更多保守控制来换可靠。
为什么窗口控制是必需的
TCP 不可能让发送方永远按自己最快速度发。问题至少有两层。
第一层是接收方未必处理得过来,所以需要接收窗口告诉发送方:“我还能接多少。”
第二层是网络本身也未必扛得住持续高速发送,所以还要有拥塞窗口控制进入网络的数据量。
这两个窗口常被混在一起,但职责不同:
- 接收窗口解决“对端来不来得及收”
- 拥塞窗口解决“网络来不来得及运”
吞吐上不去时,如果不区分这两层,很容易误判成单纯“带宽不够”。
为什么 HTTP/2 跑在 TCP 上还是会卡
HTTP/2 已经把多路复用做进应用层,同一条连接上可以并发多个请求和响应,但它下面仍然是 TCP。
这意味着:
- HTTP/2 的多个流共享同一条 TCP 字节流
- 只要前面某段 TCP 数据丢了,后续段哪怕属于别的 HTTP 流,也要等待
- 应用层多路复用并不能消除传输层按序交付带来的阻塞
这就是为什么 QUIC 会把多路复用下沉到传输层:HTTP/2 当然有流,但 TCP 只看到一条字节流。
四次挥手在解决什么
连接关闭也不是一句“断开”就够了。TCP 是全双工连接,两边都可能还有数据没发完,所以关闭要分两步进行:
一端发 FIN,表示“我这边没数据要发了”;另一端确认后,仍然可以继续把自己剩余的数据发完,最后再发送自己的 FIN。这就是四次挥手需要四个报文的原因。
所以看到连接进入 FIN_WAIT、TIME_WAIT 之类状态时,它还处在关闭流程里,而不是已经完全释放。
抓包时真正该看什么
TCP 抓包最容易失焦的地方,是一上来就看头字段大全。更有效的顺序是下面这几步。
先看连接是否建立完成
先确认:
SYNSYN+ACK- 最后的
ACK
这三步是否完整。如果三次握手没有完成,先排查网络可达性、防火墙、监听端口或中间设备,再看应用层。
再看有没有重传、重复确认和乱序
这些现象信息量很高:
RetransmissionDup ACKOut-of-OrderZero Window
它们通常指向丢包、接收端压力、链路波动或者路径质量问题。
最后看 RTT 和窗口变化
很多“时快时慢”的问题,根因不在服务端逻辑本身,而是:
- RTT 变大
- 拥塞窗口收缩
- 接收窗口变小
- 重传把发送节奏打乱
如果只盯着应用层耗时,不看这层变化,判断容易跑偏。
工程上怎么看 TCP
- TCP 不只是“可靠传输”,它的代价主要体现在连接状态、按序交付和重传控制里
- 字节流不是消息流,应用层必须自己定义消息边界
- 队头阻塞来自 TCP 的按序交付,而不是 HTTP/2 自己额外制造的
- 吞吐问题不能只看带宽,窗口、RTT、丢包和拥塞控制往往更关键
- QUIC 不是 TCP 的改名版,而是把现代 Web 更需要的能力重新放到传输层
继续阅读
- TCP 状态为什么能看出连接卡在哪:继续看 SYN、ESTABLISHED、CLOSE-WAIT 和 TIME-WAIT 怎样帮助定位连接阶段
- TCP 拥塞控制为什么让丢包后的网络变慢:继续看拥塞窗口、RTT、丢包和 RTO 怎样影响吞吐
- IP:TCP 之下负责可达性、路由和尽力而为转发的网络层
- NAT:TCP 连接穿过地址改写边界时为什么仍然依赖状态维持
- HTTP:应用层的资源语义和中间层为什么建在 TCP 之上
- HTTPS:
TCP + TLS怎样把 HTTP 放进安全通道 - QUIC:为什么现代 Web 会绕开 TCP 的部分代价