--- title: "05-传输层:TCP 与 UDP 全家桶" aliases: - 传输层 created: 2026-08-29 tags: - 基础与理论 - 计算机网络 - "408" --- # 传输层:TCP 与 UDP 全家桶 > 网络层把包裹送到了正确的**机器**,但一台机器上跑着几十个程序——微信、浏览器、游戏,给谁?传输层补上最后一段寻址(**端口**),并按需提供两种服务:**快而不稳的 UDP**、**稳而不快的 TCP**。这是 408 和面试双重核心章。 ## 一、端到端:进程到进程 - 链路层交付到"相邻节点",网络层交付到"主机",**传输层交付到"进程"** - **端口号** 0~65535 就是快递柜的格子号: ``` 一台主机 = 一栋楼(IP 地址) 端口号 = 楼里的房间号 IP + 端口 = 套接字 Socket(全网唯一的投递地址) ``` | 端口段 | 说明 | 例子 | | --- | --- | --- | | 0~1023 | 知名端口,系统保留 | 22 SSH / 53 DNS / 80 HTTP / 443 HTTPS | | 1024~49151 | 注册端口 | 3306 MySQL / 6379 Redis | | 49152~65535 | 动态/临时端口 | 客户端发起连接时随机抓一个 | > 💡 你手机上每个联网 App 出门都带一个临时端口——同一台机器上几十个连接不串线,全靠端口区分。 ## 二、UDP:快而不稳的明信片 UDP 报文头只有 **8 字节**:源端口、目的端口、长度、校验和。没了。 | 特点 | 说明 | | --- | --- | | 无连接 | 不打招呼直接发,省一次握手 | | 不可靠 | 发出去就不管了,丢了不补 | | 面向报文 | 应用给多大就发多大,不拆不并 | | 支持广播/组播 | 一对多随便发 | | 首部开销小 | 8B vs TCP 的 20B+ | **谁在用 UDP?** DNS 查询(一问一答,握手太亏)、视频通话/直播(丢一帧无所谓,重传到的旧帧反而没用)、游戏、以及 **QUIC**——HTTP/3 跑在 UDP 之上自己补可靠,UDP 已经从"不可靠"洗白成"可定制的底座"。 ## 三、TCP:稳而不快的挂号信 TCP 的四张招牌:**面向连接、可靠传输、字节流、全双工**。首部 20 字节起步,关键字段先认识一下(后面全靠它们演戏): | 字段 | 干什么 | | --- | --- | | **序号 seq** | 字节流里每个字节的编号——乱序到达也能拼回原样 | | **确认号 ack** | "你发到我这里第 X 字节之前我都收到了,期待第 X 个" | | 标志位 | **SYN**(想建连接)/ **ACK**(确认)/ **FIN**(我发完了)/ RST(重置) | | **窗口** | 接收方还剩多少缓冲——流量控制的依据 | > 🤔 "字节流"意味着:你 write 两次 100B,对方可能一次收到 200B,也可能分三次收到——TCP 只保证字节顺序对,**不保证"一条消息"的边界**(粘包问题的根源,应用层自己定边界,比如 HTTP 用 Content-Length)。 ## 四、三次握手:为什么不是两次 客户端和服务器要开始通话,先对表: ``` 客户端 服务器 | —— SYN, seq=x ————————→ | ① 我想通话(你听得见我吗) | ←— SYN+ACK, seq=y, ack=x+1| ② 听得见!你听得见我吗 | —— ACK, ack=y+1 ——————→ | ③ 听得见!开聊 ``` **为什么两次不够?** 推演一下就明白: ``` 假设只要两次握手: 客户端 —— 旧连接的 SYN(迷路的迟信)——→ 服务器 服务器收到就当"连接成立",开始分配缓冲、单方面等数据…… 但客户端早放弃这次连接了 → 服务器傻等 → 资源白占 ``` 三次的本质是**双方都确认了双向链路通**: | 已确认 | ①后 | ②后 | ③后 | | --- | --- | --- | --- | | 客户端→服务器 | ✅ | ✅ | ✅ | | 服务器→客户端 | ❌ | ✅ | ✅ | 两次时"服务器→客户端"这条路从未被确认。此外第三次握手的 ACK 还能**捎带数据**,不让服务器干等。 > ⚠️ SYN 洪水攻击:攻击者狂发 SYN 不回 ACK,服务器半连接队列被占满。现代内核有 syncookies 等防御,知道这个名词即可。 ## 五、四次挥手:分手为什么这么繁琐 数据说完了,两边都要表态"我发完了"——**双向各关一次**: ``` 客户端 服务器 | —— FIN, seq=u ————————→ | ① 我的数据发完了 | ←— ACK, ack=u+1 ——————— | ② 知道了(但我可能还没发完) | ←— FIN, seq=w ————————— | ③ 我也发完了 | —— ACK, ack=w+1 ——————— | ④ 知道了,再见 ``` **为什么挥手比握手多一次?** 因为 TCP 全双工:收到对方 FIN 只代表"对方不发了",**我可能还有数据没发完**(第②③步之间服务器还能继续发)——所以 ACK 和 FIN 分开两步。握手时 SYN 和 ACK 能合并(服务器没什么要先说的),挥手时通常合并不了。 ### TIME_WAIT:谁在原地等 2MSL 第④步之后,**主动关闭方**不立刻关门,而是等 **2MSL**(两倍报文最大寿命)才真正关闭: 1. 若最后的 ACK 丢了,服务器会重发 FIN——在等的人才能补 ACK 2. 让本连接的旧报文在网络里自然死亡,避免污染下一个同端口的新连接 > 💡 面试名场面:"服务器上大量 TIME_WAIT 正常吗?"——主动关闭方会有,短连接高并发服务(每次请求都由它主动关闭)会堆出几万个,属正常现象;优化方向是连接复用(长连接/连接池),不是消灭 TIME_WAIT。 ## 六、可靠传输:TCP 把链路层三协议的作业又抄了一遍 [[03-数据链路层|链路层]]的停止-等待/回退 N/选择重传,TCP 全部继承并升级: | 机制 | TCP 版本 | | --- | --- | | 校验 | 首部+数据一起校验 | | 编号 | **字节级**序号(不是帧号),乱序重排、去重 | | 确认 | 累积确认:"到 X 为止全收到了" | | 超时重传 | RFT 自适应:每次实测 RTT,超时值跟着网络抖动调 | | 快速重传 | 收到 **3 个重复 ACK**,不等超时立刻重发丢失段 | > 🤔 为什么链路层以太网干脆不做可靠传输、全推给 TCP?——一层做足一次就够,两层都做是浪费。这是端到端原则的经典案例。 ## 七、滑动窗口与流量控制 发送方一口气灌太多,接收方缓冲区爆了怎么办?——**让接收方定价**: ``` 接收方每次 ACK 都带上"窗口"字段:我还剩 XX 缓冲,你最多发这么多 发送方的未确认数据量 ≤ 这个窗口 —— 像给水管装了个可调阀门 ``` 接收方缓冲被读走就"开窗",塞满就"关窗"到 0;发送方收到窗口 0 后会定期发探测报文,防止"开窗通知丢了双方互相干等"的死锁。 ## 八、拥塞控制:路堵了,大家都慢点(408 必考) 流量控制管"接收方受得了吗",拥塞控制管"**网络**受得了吗"——四部曲: ``` cwnd(拥塞窗口)从 1 开始: 慢开始 → 每过一个 RTT 翻倍(1→2→4→8…指数涨)直到 ssthresh 门限 拥塞避免 → 过了门限改线性:每 RTT 只 +1(小心试探) 快重传 → 收到 3 个重复 ACK:不等超时,立刻重发丢失段 快恢复 → 门限 ssthresh = 当前 cwnd 减半,cwnd 从新门限开始线性爬 ``` 完整走一遍(ssthresh = 8): ``` 1, 2, 4, 8 ← 慢开始指数涨,到门限 8 9, 10, 11 ← 拥塞避免线性爬 (丢包,3 个重复 ACK) 门限 = 11/2 ≈ 5,cwnd = 5 → 6, 7, 8… ← 快重传+快恢复 (若超时丢包:门限减半,cwnd 归 1 从头慢开始) ``` > 💡 发送方真正能发多少 = **min(接收窗口, 拥塞窗口)**——接收方的意愿和网络的承受力,取小的那个。 ## 九、TCP vs UDP 终极对比 | | TCP | UDP | | --- | --- | --- | | 连接 | 面向连接(握手) | 无连接 | | 可靠性 | 可靠(确认+重传+排序) | 尽力而为 | | 形态 | 字节流(无边界,有粘包) | 面向报文(有边界) | | 首部 | 20B 起 | 8B | | 收发速度 | 慢(机制换稳定) | 快 | | 一对多 | 不支持 | 支持广播/组播 | | 场景 | 网页/文件/邮件/登录 | DNS/视频/游戏/QUIC | 一句话:**要"保证送到"用 TCP,要"保证及时"用 UDP**——视频会议宁可丢帧也不想要迟到三秒的"可靠"旧帧。 ## 十、盲点自测 1. **端口解决什么问题?** ——主机内的进程寻址;IP+端口=套接字,全网唯一的投递地址。 2. **三次握手的本质?** ——双方都确认双向链路通;两次时服务器→客户端方向未被确认,且迟到的旧 SYN 会造成服务器单方面空等。 3. **四次挥手为什么比握手多一次?** ——全双工,ACK 和 FIN 常无法合并(收到对方 FIN 后自己可能还有数据)。 4. **TIME_WAIT 为什么要等 2MSL?** ——给"最后的 ACK 丢失后重传 FIN"留补票机会 + 让旧报文自然死亡。 5. **TCP 怎么实现可靠?** ——字节编号 + 累积确认 + 超时/快速重传 + 滑动窗口,乱序缓存重排。 6. **流量控制和拥塞控制分别管谁?** ——前者管接收方的缓冲(接收窗口),后者管网络的承载(拥塞窗口);实际发送量取两者小值。 7. **慢开始"慢"在哪?** ——起点慢(从 1 开始),增长并不慢(指数翻倍);过了 ssthresh 才转线性。 8. **什么时候 UDP 反而是对的选择?** ——实时性优先的场景:视频/语音/游戏/DNS;丢一个旧帧比等重传更划算。 9. **粘包是什么?** ——TCP 是字节流没有消息边界;应用层要自己定界(Content-Length / 分隔符)。 ## 十一、动手玩一把 ```bash # 用 nc 亲手体验"服务器/客户端" nc -l 9000 # 终端A:起一个监听 9000 端口的服务器 nc 127.0.0.1 9000 # 终端B:连上去,A/B 互打字即收(TCP 全双工) # 看本机现有连接和端口占用 netstat -an | grep -E "ESTABLISHED|LISTEN" | head lsof -i :443 | head # 谁占着 443 # 抓包看三次握手(Linux/macOS 通用) # tcpdump -i any port 9000 跑着的同时用 nc 连一次,肉眼数 SYN/SYN+ACK/ACK ``` --- 可靠管道铺好了,但管道里说的"话"(网页、域名、邮件)各是什么格式?最后一层 → [[06-应用层]] ⬅️ [[04-网络层|网络层]] 🏠 [[00-基础与理论|00-基础与理论]] ➡️ [[06-应用层|应用层]]