4、传输层协议:TCP三次握手与四次挥手、TCP滑动窗口与拥塞控制、UDP协议特点与应用场景
传输层,说白了就是给应用层的数据包「贴标签」和「做担保」的。我做了这么多年网络,见过太多因为不懂传输层而踩坑的案例。今天咱们就把TCP和UDP这两个老伙计彻底聊透。
4.1 TCP三次握手:建立连接的门票
TCP是面向连接的协议。什么叫面向连接?就是通信前先打个招呼,确认双方都在线,而且愿意聊天。这个打招呼的过程,就是三次握手。
我习惯把三次握手比作打电话:
- 第一次握手(SYN):客户端说「喂,能听到吗?」——发送SYN包,进入SYN_SENT状态。
- 第二次握手(SYN+ACK):服务端回「能听到,你听到我吗?」——发送SYN+ACK包,进入SYN_RCVD状态。
- 第三次握手(ACK):客户端说「听到了,咱们开始聊吧」——发送ACK包,双方进入ESTABLISHED状态。
核心要点:三次握手不仅仅是确认双方在线,更重要的是协商初始序列号(ISN)。序列号是TCP可靠传输的基石,没有它,数据包就乱套了。
我在项目中遇到过一个问题:某次压测时,大量连接处于SYN_RCVD状态,导致新连接无法建立。排查后发现是客户端发完SYN后没收到SYN+ACK,一直在重试。嗯,这里要注意——半连接队列(SYN Queue)满了,服务端就会丢弃新来的SYN包。
避坑指南:我曾经在配置Nginx时,把net.ipv4.tcp_syncookies设为0,结果高并发下直接炸了。建议生产环境务必开启SYN Cookies(设为1),它能有效防止SYN Flood攻击。
4.2 四次挥手:优雅地告别
断开连接比建立连接复杂一点,需要四次挥手。为什么是四次?因为TCP是全双工的,双方都要独立关闭自己的通道。
流程是这样的:
- 第一次挥手(FIN):主动方说「我这边没数据要发了」——发送FIN包,进入FIN_WAIT_1状态。
- 第二次挥手(ACK):被动方回「收到,但我还有数据要发」——发送ACK包,进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2状态。
- 第三次挥手(FIN):被动方数据发完了,说「我也没数据了」——发送FIN包,进入LAST_ACK状态。
- 第四次挥手(ACK):主动方回「收到,拜拜」——发送ACK包,进入TIME_WAIT状态,等待2MSL后关闭。
注意:TIME_WAIT状态会持续2MSL(约1-4分钟)。你想想看,如果服务端频繁主动关闭连接,会产生大量TIME_WAIT,占用端口资源。我曾经在短连接场景下,看到一台机器上有几万个TIME_WAIT,差点把端口池打爆。
解决方案?我建议用net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle(注意:内核4.12后已废弃recycle)。或者干脆改用长连接,减少挥手次数。
4.3 TCP滑动窗口:流量控制的艺术
滑动窗口是TCP实现流量控制的核心机制。说白了,就是接收方告诉发送方:「你一次最多发这么多,别把我撑爆了」。
窗口大小由接收方的接收缓冲区剩余空间决定。发送方根据这个窗口大小,决定一次能发多少数据。窗口是动态变化的,所以叫「滑动」。
我画个图帮你理解:
窗口越大,吞吐量越高。但窗口太大,接收方可能扛不住。我见过一个案例:某视频平台直播卡顿,排查发现是接收窗口被调得太大,导致接收缓冲区溢出,大量丢包重传。后来把窗口调小,问题就解决了。
调优建议:Linux下可以通过net.core.rmem_max和net.core.wmem_max调整缓冲区大小。我个人习惯在BGP场景下把窗口调大,在IoT场景下调小,具体看业务。
4.4 拥塞控制:别把网络堵死
滑动窗口管的是点对点的流量,拥塞控制管的是整个网络的流量。说白了,就是防止你发得太猛,把中间路由器搞崩溃。
TCP拥塞控制有四个阶段:
| 阶段 | 触发条件 | 行为 |
|---|---|---|
| 慢启动 | 连接刚建立 | 拥塞窗口从1开始,每收到一个ACK翻倍(指数增长) |
| 拥塞避免 | 窗口达到慢启动阈值(ssthresh) | 每收到一个ACK,窗口增加1(线性增长) |
| 快速重传 | 收到3个重复ACK | 立即重传丢失的包,不等超时 |
| 快速恢复 | 快速重传后 | ssthresh减半,窗口设为ssthresh+3,继续线性增长 |
你想想看,如果网络已经堵了,你还拼命发,那不是火上浇油吗?TCP的拥塞控制就是通过动态调整发送速率,让网络慢慢恢复。
我记得有一次线上事故,某服务突然大量超时。排查发现是网络设备故障导致丢包率飙升,TCP进入拥塞控制后窗口急剧缩小,吞吐量掉到原来的十分之一。后来加了冗余链路,才解决了这个问题。
核心原则:TCP拥塞控制遵循「加法增、乘法减」(AIMD)原则。没丢包就慢慢加窗口,丢包了就狠心减半。这个设计很巧妙,保证了网络的公平性和稳定性。
4.5 UDP协议:简单粗暴,但快
UDP和TCP完全是两个极端。TCP像快递员,保证送到、保证顺序、还给你回执。UDP像扔飞镖,扔出去就不管了,能不能中看运气。
UDP的特点:
- 无连接:不需要三次握手,直接发数据
- 不可靠:不保证送达,不保证顺序,不保证不重复
- 无拥塞控制:不管网络堵不堵,照发不误
- 头部开销小:只有8字节(TCP是20字节)
那UDP有什么用?说白了,有些场景根本不在乎那点丢包,在乎的是低延迟。
典型的应用场景:
| 场景 | 为什么用UDP |
|---|---|
| 视频直播/会议 | 丢一帧画面没关系,但延迟高了体验就差了 |
| 在线游戏 | 玩家位置信息需要实时更新,重传旧数据没意义 |
| DNS查询 | 查询包很小,重传成本低,用TCP反而慢 |
| 物联网传感器 | 数据量小,丢包了下次再发就行 |
我在做游戏服务器时,就用UDP传输玩家位置信息。如果某个包丢了,直接发下一个位置就行,重传旧位置反而会让角色「瞬移」。但关键操作(比如购买道具)必须用TCP,保证不丢。
注意:UDP没有拥塞控制,如果发得太猛,可能会把网络打爆。我曾经见过一个UDP广播应用,因为没做限速,直接把公司内网干瘫痪了。所以用UDP时,应用层一定要自己做流量控制。
4.6 TCP vs UDP:怎么选?
选TCP还是UDP,其实就一句话:你要可靠性,还是低延迟?
我个人的选择原则:
- 需要数据完整、顺序正确 → TCP(文件传输、网页、邮件)
- 能容忍少量丢包,但要求实时 → UDP(音视频、游戏、监控)
- 不确定?先用TCP,性能不够再考虑UDP
嗯,这里要补充一点:现在很多应用用UDP做传输,然后在应用层自己实现可靠性(比如QUIC协议)。说白了,就是既要UDP的低延迟,又要TCP的可靠性。这种「自定义传输协议」在大型系统中越来越常见。
实战建议:如果你在开发高并发服务,建议先搞清楚业务对可靠性的容忍度。我曾经帮一个团队优化过,他们用TCP传实时日志,结果因为重传导致延迟飙升。后来改成UDP+应用层ACK,延迟降了80%。
好了,传输层的内容就聊到这儿。TCP和UDP各有千秋,关键是要理解它们的本质区别,然后根据场景做选择。下次遇到网络问题,先想想是三次握手没握上,还是滑动窗口太小,还是拥塞控制太激进——对症下药,问题就好解决了。
本章总结:
- 三次握手建立连接,四次挥手断开连接
- 滑动窗口控制点对点流量,拥塞控制控制流量
- UDP无连接、不可靠、低延迟,适合实时场景
- 选协议时,在可靠性和延迟之间做权衡