前言
IP 负责把包送到目标主机,TCP 负责把数据可靠送到目标主机上的具体进程。
一句话:
TCP 在不可靠的 IP 之上,提供可靠、有序、面向连接的字节流传输。
这篇文章围绕 TCP 的完整生命周期展开:
1 | 端口定位进程 |
1. TCP 不只是建立连接
很多人第一次学 TCP,会把注意力放在三次握手上,于是误以为:
1 | TCP 只是用来建立连接。 |
其实不是。
三次握手只是 TCP 生命周期的开始。TCP 更主要的工作是:
1 | 在连接建立后,持续承载应用层字节流。 |
比如:
- HTTP 请求
- HTTP 响应
- TLS 握手数据
- 加密后的 HTTPS 数据
这些都可以通过 TCP 传输。
所以 TCP 做两类事:
1 | 1. 控制连接:三次握手、四次挥手、ACK、窗口等。 |
这些一段一段的数据通常叫:
1 | TCP 段 / TCP 报文段 |
2. 端口:找到目标进程
IP 地址只能找到目标主机。
但一台主机上可能同时跑很多服务:
1 | 80:HTTP |
TCP 通过端口找到具体进程。
比如访问:
1 | https://example.com |
DNS 解析后得到服务器 IP,浏览器会连接:
1 | 目标 IP:443 |
客户端自己也会分配一个临时端口:
1 | 192.168.1.10:52341 -> 93.184.216.34:443 |
一条 TCP 连接通常可以用五元组标识:
1 | 源 IP |
所以:
1 | IP 找主机 |
3. 三次握手:建立连接
TCP 三次握手不是为了发送 HTTP,而是为了建立 TCP 连接。
典型过程是:
1 | 客户端 -> 服务端:SYN,seq = x |
三次握手确认了三件事:
1 | 双方都能发送 |
第一次 SYN:
1 | 客户端说:我想建立连接,我的初始序号是 x。 |
服务端收到后能确认:
1 | 客户端发送能力正常 |
第二次 SYN + ACK:
1 | 服务端说:我收到了你的 SYN,我的初始序号是 y。 |
客户端收到后能确认:
1 | 服务端发送能力正常 |
第三次 ACK:
1 | 客户端说:我收到了你的 SYN,我确认你的序号 y。 |
服务端收到后,才确认客户端收到了自己的回应。
所以三次握手的核心目的:
1 | 确认双方收发能力 |
4. 三次握手有没有 HTTP 数据
通常没有。
三次握手传的是 TCP 控制报文:
1 | SYN |
普通 HTTP 流程是:
1 | TCP 三次握手 |
HTTPS 流程是:
1 | TCP 三次握手 |
但 TCP 握手包本身也要真实经过网络:
1 | TCP SYN |
也就是说:
TCP 握手不是应用层数据,但它仍然要被 IP、数据链路层和物理层承载。
5. TCP 为什么会有报文长度
TCP 不是只建连接,它还要传输应用层数据。
应用层可能给 TCP 一大段数据:
1 | HTTP 响应体:1MB |
TCP 不会把 1MB 一口气塞进一个包里发出去,因为底层链路有 MTU 限制。
以太网常见 MTU 是:
1 | 1500 字节 |
IP 头和 TCP 头通常各占:
1 | IP 头:20 字节 |
所以一个 TCP 段里能装的应用数据通常大概是:
1 | 1500 - 20 - 20 = 1460 字节 |
这个值常叫:
1 | MSS,Maximum Segment Size |
于是 TCP 会把应用层字节流切成多个 TCP 段:
1 | 第 1 段:Seq = 1000,Len = 1460 |
这里的 Len 就是这段 TCP 承载了多少字节的应用数据。
三次握手里的 SYN、ACK 通常没有应用数据,所以数据长度通常是 0。
但注意:
SYN 和 FIN 虽然不带应用数据,但会消耗一个序号。
6. TCP 是字节流协议
TCP 不关心 HTTP 是一条消息还是两条消息。
它只看到一串字节:
1 | GET / HTTP/1.1\r\nHost: example.com\r\n\r\n |
然后根据 MSS、窗口、拥塞控制等情况,切成合适大小的 TCP 段发送。
所以 TCP 的序号不是“第几个包”,而是:
1 | 字节序号 |
比如一段数据:
1 | Seq = 1000 |
表示:
1 | 这段数据覆盖字节 1000 ~ 1499 |
7. ACK 与累计确认
ACK 是接收方告诉发送方:
1 | 我下一个希望收到哪个字节。 |
比如接收方回:
1 | ACK = 1500 |
意思不是“我收到了第 1500 个字节”,而是:
1 | 1500 之前的字节我都收到了。 |
这叫累计确认。
比如发送方连续发:
1 | Seq = 1000, Len = 500 覆盖 1000 ~ 1499 |
如果接收方都收到了,可以直接回:
1 | ACK = 2500 |
表示:
1 | 2500 之前的字节都收到了。 |
8. 丢包、重传、乱序和去重
IP 层是不可靠的,可能丢包、乱序、重复。
TCP 通过序号和 ACK 处理这些问题。
如果中间一段丢了:
1 | 1000 ~ 1499 到了 |
接收方不能确认到 2500,只能继续回:
1 | ACK = 1500 |
意思是:
1 | 我还在等 1500 开始的数据。 |
发送方发现一直收到重复 ACK,或者等超时了,就会重传缺失的数据。
常见重传机制:
1 | 超时重传:一段时间没收到 ACK,就重传。 |
乱序数据可以先缓存,等缺失部分到齐后再按顺序交给应用层。
重复数据可以根据序号识别并丢弃。
所以 TCP 可靠性的本质是:
1 | Seq 标记我发到哪 |
9. 传输数据不是“两次握手”
三次握手之后,TCP 连接已经建立。
之后传输数据时,确实经常能看到这样的节奏:
1 | 发送方 -> 接收方:DATA |
但这不叫“两次握手”。
握手是连接建立阶段的动作:
1 | SYN |
数据阶段是在已建立连接上发送数据,并用 ACK 确认对方收到哪里。
比如客户端发:
1 | Seq = 1000 |
服务端回:
1 | ACK = 1500 |
意思是:
1 | 1000 ~ 1499 我收到了,下一个请从 1500 开始。 |
这只是确认机制,不是重新建立连接。
并且 TCP 不是每发一段都必须等 ACK 才能发下一段。只要窗口允许,发送方可以连续发送多段数据,接收方再用 ACK 累计确认。
所以:
三次握手建立连接;数据传输阶段是 DATA 和 ACK 持续流动。
10. TCP 是全双工的
TCP 建立连接后,客户端和服务端可以同时发送数据。
因为 TCP 是全双工:
1 | 客户端 -> 服务端 可以发 |
这两个方向各自有独立的序号空间。
比如三次握手时:
1 | 客户端初始序号 = 1000 |
客户端发数据:
1 | 客户端 -> 服务端 |
服务端同时也可以发数据:
1 | 服务端 -> 客户端 |
这不会冲突,因为:
1 | Seq 管自己这个方向发出的字节位置 |
服务端发数据时,也可以顺便确认客户端的数据:
1 | 服务端 -> 客户端 |
意思是:
1 | 我发给你 1200 字节数据。 |
这叫:
1 | 捎带确认 |
普通 HTTP/1.1 看起来像:
1 | 客户端请求 |
但这是 HTTP 的请求-响应模型,不代表 TCP 是半双工。
WebSocket 更能体现 TCP 的全双工能力:
1 | 客户端可以随时发消息 |
底层 TCP 支持双方同时传输。
11. 滑动窗口:提高效率
如果 TCP 每发一段数据都等一个 ACK,效率会很低:
1 | 发 1 段 |
滑动窗口允许发送方在没收到 ACK 前,连续发送一定范围内的数据。
比如窗口大小是 3000 字节,可以连续发:
1 | 1000 ~ 1999 |
收到:
1 | ACK = 3000 |
说明 3000 之前都收到了,窗口就可以继续往后滑:
1 | 4000 ~ 4999 |
所以滑动窗口解决的是:
1 | 既保证可靠,又提高吞吐量。 |
12. 流量控制:别撑爆接收方
流量控制关注的是:
1 | 接收方还能不能接得住? |
接收方会告诉发送方:
1 | 我现在还能接多少数据。 |
这个值叫:
1 | 接收窗口 rwnd |
比如:
1 | rwnd = 8000 |
意思是:
1 | 你最多还能发 8000 字节,别再多了。 |
流量控制保护的是接收方。
13. 拥塞控制:别撑爆网络
拥塞控制关注的是:
1 | 网络还能不能扛得住? |
接收方可能很强,但中间网络可能堵了:
1 | 路由器队列满 |
TCP 会根据 ACK、丢包、延迟等信号动态调整发送速度。
这个控制量叫:
1 | 拥塞窗口 cwnd |
最终发送方真正能发多少,取决于:
1 | min(rwnd, cwnd) |
常见过程:
1 | 慢启动:从小窗口开始,指数增长,先试探网络。 |
流量控制和拥塞控制的区别:
| 对比 | 流量控制 | 拥塞控制 |
|---|---|---|
| 关注对象 | 接收方 | 网络 |
| 问题 | 接收方缓冲区会不会满 | 中间链路/路由器会不会堵 |
| 控制依据 | 接收窗口 rwnd | 拥塞窗口 cwnd |
| 目的 | 别撑爆接收方 | 别撑爆网络 |
一句话:
1 | 流量控制看对方吃不吃得下 |
14. 四次挥手:关闭连接
TCP 是全双工的:
1 | 客户端可以发给服务端 |
关闭时要分别确认两个方向都不再发送数据。
假设客户端主动关闭:
1 | 客户端 -> 服务端:FIN |
含义是:
1 | 客户端:我不发了 |
有时第二次 ACK 和第三次 FIN 可以合并,看起来像三次,但逻辑上仍然是四个动作:
1 | 确认客户端关闭 |
15. TIME_WAIT 是什么
主动关闭的一方在发完最后一个 ACK 后,不会立刻消失,而是进入:
1 | TIME_WAIT |
主要原因有两个:
1 | 确保最后一个 ACK 能到达。 |
如果最后一个 ACK 丢了,服务端会重发 FIN。
主动关闭方在 TIME_WAIT 状态下还能再次回复 ACK。
所以:
1 | 谁主动关闭,谁通常进入 TIME_WAIT。 |
16. HTTP 响应结束不等于 TCP 关闭
HTTP 请求结束,不一定马上 TCP 四次挥手。
因为 HTTP/1.1 支持长连接:
1 | 一个 TCP 连接可以复用多个 HTTP 请求。 |
可能是:
1 | 请求 A |
所以:
1 | HTTP 响应结束 ≠ TCP 连接马上关闭 |
17. 一句话总结
TCP 可以用这条线记:
1 | 端口:找到目标进程 |
最短总结:
TCP 不是靠网络本身可靠,而是靠连接、序号、确认、重传、窗口和拥塞控制,在不可靠的 IP 之上提供可靠有序的字节流。