运输层概述
运输层概述
一个问题:包到了电脑之后交给谁
你家电脑有 5 个应用同时在跑——Chrome、Safari、Spotify、微信、邮件。包从远端服务器到你的电脑,IP 层已经把包送到了正确机器。但这个包是给哪个应用的?
这就是运输层要解决的问题。运输层在 IP 层(网络层)之上,给每个应用分配一个端口号作为标识。包到主机后,TCP/UDP 头里的端口号告诉主机"这个包给 Chrome,不是给微信"。
端口号是运输层给"应用"的逻辑地址。0-1023 是知名端口(HTTP=80, HTTPS=443, SSH=22, DNS=53),1024-65535 是临时端口(客户端用)。
TCP 和 UDP:两种不同的运输服务
运输层有两大主流协议,定位完全不同:
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接,先握手 | 无连接,直接发 |
| 可靠 | 确认、重传、排序 | 不保证到达、不保证顺序 |
| 流量控制 | 滑动窗口 | 无 |
| 拥塞控制 | 慢启动等 | 无 |
| 头部 | 20-60 字节 | 8 字节 |
| 速度 | 慢(多次握手) | 快(直接发) |
| 用途 | HTTP、SSH、邮件、文件传输 | DNS、视频通话、游戏、QUIC |
TCP 适合"宁可慢不能错":文件下载、网页、邮件、SSH。一个字节错了整个文件就坏。
UDP 适合"宁可丢不能慢":视频通话、游戏、DNS。偶尔丢一帧可以接受,但延迟 200ms 视频就卡。
用错场景的后果:
- 用 TCP 做视频通话:网络抖动触发重传,旧帧堆在新帧后面,延迟飙升
- 用 UDP 做文件下载:丢包就漏字节,文件 MD5 对不上
端口号:应用的"门牌号"
端口号是 16 位整数(0-65535)。0 不使用,1-1023 是知名端口,1024-49151 是注册端口,49152-65535 是动态/私有端口。
服务器 listen 一个固定端口(如 HTTP server listen 80),客户端连这个端口。客户端自己用什么端口?系统从 49152-65535 范围随机分配一个。
Wireshark 抓包看 Source port: 54321, Dest port: 443:客户端从临时端口 54321 连服务器的 HTTPS 443 端口。
一个服务器一个端口只能有一个进程 listen?Linux 3.9+ 引入 SO_REUSEPORT,允许多个进程 listen 同一个端口,内核做负载均衡。Nginx 配置 reuseport 能让多 worker 进程分担连接。
socket:一台机器上的"网络端点"
socket = IP + 端口,标识一个通信端点。192.168.1.100:54321 是个 socket,93.184.216.34:443 也是个 socket。一个 TCP 连接由 五元组 唯一标识:
(协议, 源 IP, 源端口, 目的 IP, 目的端口)
(TCP, 192.168.1.100, 54321, 93.184.216.34, 443)五元组完全相同的两条连接不存在(端口耗尽除外)。这也是为什么服务器同 IP 同端口能服务成千上万个客户端——每个客户端的五元组都不同。
ss -tnp 看 Linux 当前所有 TCP 连接,每条都显示五元组。
TCP 头结构
TCP 头部 20 字节固定,加可选字段最多 60 字节:
TCP 头部结构:
图注:固定头部 20 字节,加选项最多 60 字节。标志位 URG/ACK/PSH/RST/SYN/FIN 控制连接状态。
关键字段:
- 源端口/目的端口:应用层标识
- 序列号:这个包的数据在字节流中的位置
- 确认号:告诉对方"我收到了到 X 为止的所有数据,下一个期望 X+1"
- 数据偏移:TCP 头长度(4 位,单位 4 字节,所以 20-60 字节)
- 标志位:6 个标志位 URG/ACK/PSH/RST/SYN/FIN,控制连接状态
- 窗口大小:告诉对方我的接收窗口(流量控制)
- 校验和:校验头和数据
- 选项:MSS、窗口缩放、时间戳、SACK 等
UDP 头结构
UDP 头只有 8 字节,极简:
UDP 头部结构:
图注:UDP 头只有 8 字节,没有序列号、确认号、窗口、标志位。校验和在 IPv4 下可选,IPv6 下强制。
没有序列号、没有确认号、没有窗口、没有标志位。UDP 只是个"发包工具",什么都不保证。
UDP 校验和是可选的(IPv4 下可关闭,IPv6 强制)。很多应用关闭校验和让 UDP 更快,但代价是丢包/错误不感知。
真实场景:选 TCP 还是 UDP
游戏服务器选择最纠结。
MMORPG(大型多人在线 RPG):玩家位置同步是关键。早期用 UDP 自实现可靠传输,但网易、暴雪最终都切回 TCP——TCP 的可靠保证省了无数 bug 修复时间。延迟 50-100ms 对 RPG 可接受。
FPS(第一人称射击):玩家视角必须最新。1 帧延迟 200ms 就完了。用 UDP + 自实现 KCP/QUIC 类协议,丢一帧就丢,但延迟可控。
手游 MOBA:类似 FPS,技能命中判定、伤害结算需要同步。用 UDP 居多,丢包率 1% 以下可用。
视频会议:用 UDP + RTP/RTCP,丢包隐藏算法(PLC)让画面勉强连贯。
直播:用 RTMP(TCP)或 QUIC(UDP)。直播可以缓冲几秒,对实时性要求低。
文件下载:必须 TCP。HTTP/FTP 都基于 TCP。HTTP/3 把 HTTP 嫁接到 QUIC(基于 UDP),但语义上是"可靠传输"。
现代运输层的发展
QUIC:Google 2012 年设计,2018 年 IETF 标准化(RFC 9000)。基于 UDP,把 TCP 的可靠传输 + TLS 1.3 加密 + HTTP/2 多路复用合到一个协议里。0-RTT 握手让延迟降低 1 RTT。
HTTP/3:HTTP 协议跑在 QUIC 之上,Chrome/Firefox/Safari 都已经支持。Cloudflare、Akamai 全面部署 HTTP/3。
SCTP:Stream Control Transmission Protocol。1999 年为电信信令设计,支持多流(一个连接多独立流)、多宿主(一个连接多 IP)。电信领域用,互联网很少。
DCCP:Datagram Congestion Controlled Protocol。UDP 加拥塞控制,但保留 UDP 的低延迟。流媒体领域。
MPTCP:Multipath TCP。让一个 TCP 连接同时用多个网络接口(Wi-Fi + 4G)。Apple iOS、Samsung Android 都用 MPTCP 提升下载速度。
思考题
- 一台服务器 80 端口能不能给 1000 个客户端同时服务?从内核的角度(socket、端口、连接)解释为什么。
- UDP 头部 8 字节,TCP 头部至少 20 字节。假设你用 UDP 发送 100 字节有效数据,UDP 总包 108 字节。如果改用 TCP,相同有效数据要传多少字节?这对你的应用协议设计有什么启示?
- 一个 Web 服务器 listen 80 端口。客户端每次连接浏览器用临时端口(如 54321、54322)。服务器怎么区分这些连接?内核用什么数据结构维护?
- 假设你设计一个低延迟实时应用(FPS 游戏)。TCP 和 UDP 都不能直接用,需要自实现可靠传输。你会怎么设计?(提示:序列号、确认、重传窗口、加密、拥塞控制)
延伸阅读
- 《深入浅出计算机网络》(高军)第 5 章
- 《UNIX 网络编程 卷 1》(Stevens)——socket 编程圣经
- RFC 793: TCP
- RFC 768: UDP
- RFC 9000: QUIC
- 动手实验:
ss -tnp看 Linux 当前所有 TCP 连接和五元组 - 动手实验:
ss -unp看所有 UDP 连接(UDP 端点不持久,"连接"是临时的)