4、传输层协议:TCP协议特点与报文段结构、三次握手与四次挥手、滑动窗口与流量控制、UDP协议特点与应用场景
传输层,说白了就是给应用层的数据包「贴标签」和「做服务」的。你想想看,网络层只负责把数据从一个机器送到另一个机器,但机器上跑着那么多进程,数据该给谁?这就是传输层要解决的问题。
我个人习惯把传输层比作快递公司。TCP 是顺丰,可靠但慢一点;UDP 是普通快递,快但可能丢件。做嵌入式开发,选哪个协议,直接决定了你产品的稳定性和实时性。
TCP 协议:可靠传输的基石
TCP 的全称是 Transmission Control Protocol,传输控制协议。它的核心就两个字:可靠。
我在项目中遇到过很多次,刚毕业的工程师觉得 TCP 太复杂,想用 UDP 自己加重传机制。结果呢?项目延期,bug 不断。TCP 的可靠性是经过几十年验证的,别轻易自己造轮子。
TCP 的特点
- 面向连接:通信前必须先建立连接,通信结束后释放连接。
- 全双工通信:双方可以同时发送和接收数据。
- 可靠传输:无差错、不丢失、不重复、按序到达。
- 流量控制:根据接收方的能力调整发送速度。
- 拥塞控制:根据网络状况调整发送速度。
核心要点:TCP 的可靠性不是免费的午餐。它需要额外的头部开销(20字节),需要三次握手的延迟,需要维护状态机。在资源受限的嵌入式设备上,这些成本必须考虑进去。
TCP 报文段结构
TCP 报文段的结构,我建议你死记硬背也要记住。因为调试网络问题时,抓包分析全靠它。
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 | 16位 | 发送方的端口号 |
| 目的端口 | 16位 | 接收方的端口号 |
| 序号 | 32位 | 本报文段数据的第一个字节的序号 |
| 确认号 | 32位 | 期望收到对方下一个报文段的序号 |
| 数据偏移 | 4位 | TCP头部长度,单位是32位字 |
| 标志位 | 6位 | URG、ACK、PSH、RST、SYN、FIN |
| 窗口 | 16位 | 接收窗口大小,用于流量控制 |
| 校验和 | 16位 | 检验整个TCP报文段 |
| 紧急指针 | 16位 | 紧急数据的结束位置 |
嗯,这里要注意:序号和确认号是 TCP 可靠传输的核心。序号不是报文段的编号,而是字节的编号。比如你发送 1000 字节的数据,序号是 100,那下一个报文段的序号就是 1100。
三次握手:建立连接
为什么是三次?不是两次,也不是四次?
我曾经在面试时被这个问题问住过。后来想明白了:三次握手是为了防止「已失效的连接请求报文段」突然又传到服务器。
流程很简单:
- 客户端发送 SYN:客户端发送 SYN=1,seq=x 的报文段,进入 SYN_SENT 状态。
- 服务器回复 SYN+ACK:服务器收到后,发送 SYN=1,ACK=1,seq=y,ack=x+1 的报文段,进入 SYN_RCVD 状态。
- 客户端发送 ACK:客户端收到后,发送 ACK=1,seq=x+1,ack=y+1 的报文段,进入 ESTABLISHED 状态。服务器收到后也进入 ESTABLISHED 状态。
实战经验:在嵌入式设备上,我建议把 SYN 超时重传时间设短一点。默认是 1 秒,但在局域网内,200 毫秒就够。这样可以加快连接建立速度。
四次挥手:释放连接
为什么挥手要四次?因为 TCP 是全双工的,每一方的连接都需要单独关闭。
- 客户端发送 FIN:客户端发送 FIN=1,seq=u 的报文段,进入 FIN_WAIT_1 状态。
- 服务器回复 ACK:服务器收到后,发送 ACK=1,seq=v,ack=u+1,进入 CLOSE_WAIT 状态。客户端收到后进入 FIN_WAIT_2 状态。
- 服务器发送 FIN:服务器数据发送完毕后,发送 FIN=1,ACK=1,seq=w,ack=u+1,进入 LAST_ACK 状态。
- 客户端回复 ACK:客户端收到后,发送 ACK=1,seq=u+1,ack=w+1,进入 TIME_WAIT 状态。等待 2MSL 后关闭。
避坑指南:我曾经在做一个物联网网关项目时,发现设备频繁断连。抓包一看,TIME_WAIT 状态堆积了上千个。原因是客户端主动关闭连接后,没有等 2MSL 就重新连接了。解决方案是让服务器主动关闭连接,或者缩短 TIME_WAIT 时间。
滑动窗口与流量控制
滑动窗口,说白了就是接收方告诉发送方:「你一次最多发这么多,别撑着我。」
接收方会在 TCP 头部的「窗口」字段中填入自己的接收缓冲区剩余大小。发送方根据这个值调整自己的发送窗口。
举个例子:
- 接收方窗口大小是 1000 字节
- 发送方发送了 500 字节,收到 ACK 确认了 300 字节
- 那么发送窗口向前滑动 300 字节,还可以发送 800 字节
我建议你在嵌入式设备上实现 TCP 时,注意窗口大小的设置。太小了影响吞吐量,太大了浪费内存。一般设置为 MSS(最大报文段长度)的 2-4 倍比较合适。
UDP 协议:简单高效的传输
UDP,User Datagram Protocol,用户数据报协议。它和 TCP 完全相反:无连接、不可靠、尽最大努力交付。
UDP 的报文段结构简单得令人发指:
| 字段 | 长度 | 说明 |
|---|---|---|
| 源端口 | 16位 | 发送方的端口号 |
| 目的端口 | 16位 | 接收方的端口号 |
| 长度 | 16位 | UDP报文段的总长度 |
| 校验和 | 16位 | 检验整个UDP报文段,可选 |
只有 8 字节的头部!相比 TCP 的 20 字节,省了一半多。
UDP 的应用场景
UDP 适合以下场景:
- 实时性要求高:比如视频通话、在线游戏。丢一帧画面没关系,但延迟不能高。
- 广播/多播:UDP 支持一对多通信,TCP 不支持。
- 简单查询:比如 DNS 查询,一个请求一个响应,用 TCP 太浪费。
- 资源受限设备:嵌入式设备内存小,跑 TCP 状态机可能吃不消。
我的建议:在嵌入式项目中,如果数据量小、实时性要求高、可以容忍偶尔丢包,果断用 UDP。比如传感器数据采集,每秒上报一次温度值,丢一两个包完全不影响。
TCP vs UDP:如何选择?
我经常被问到这个问题。其实很简单:
- 需要可靠传输?选 TCP。
- 需要实时性?选 UDP。
- 不确定?先选 TCP,性能不够再优化。
但要注意,UDP 也可以实现可靠传输,只需要在应用层加确认和重传机制。比如 QUIC 协议就是在 UDP 上实现的可靠传输。不过,这需要你投入额外的开发成本。
最后说一句:传输层的选择没有银弹。我见过用 TCP 做视频传输的,也见过用 UDP 做文件传输的。关键是要理解你的业务需求,然后做出权衡。