在之前的博客中我们提到,网络协议栈是分层设计的,数据自上而下传输时,每一层都会在数据前添加自己的首部(封装);自下而上上传时,每一层都会剥掉对应首部(解封装)。
一、传输层基础:分层封装与通信标识
1. 分层封装的完整报文结构
数据从应用层发出后,会逐层向下封装:
- 应用层:原始业务数据
- 传输层:添加 TCP/UDP 首部 → 称为段(Segment)
- 网络层:添加 IP 首部 → 称为包 / 分组(Packet)
- 数据链路层:添加帧头 + 帧尾(FCS 校验) → 称为帧(Frame)
因此数据链路层的完整报文结构是:链路层帧头 + IP 首部 + TCP/UDP 首部 + 应用层数据 + 链路层帧尾。
2. 五元组:唯一标识一次通信
IP 首部包含源 IP、目的 IP、协议号;传输层首部包含源端口、目的端口。这五个字段合称为五元组,可以在全网范围内唯一标识一条双向通信。
- 源 IP、目的 IP:标识通信的两台主机
- 协议号:标识上层使用的传输层协议(TCP 对应 6,UDP 对应 17),注意这是协议类型编号,不是 IP 协议版本
- 源端口、目的端口:标识主机上的具体应用进程
3. 端口号基础
端口号是传输层用来区分本机不同进程的标识,长度 16 位,取值范围 0~65535,分为三类:
- 查看标准服务对应的端口映射:cat /etc/services
- 查看当前系统已绑定的端口 / 进程占用:ss -tulnp、netstat -tulnp、lsof -i :端口号
二、UDP 协议:面向数据报的不可靠传输
学习传输层协议,核心就是学习它的首部格式和协议行为—— 所有特性都由首部字段和内核处理逻辑决定。
1. UDP 报文首部结构
UDP 首部非常精简,固定 8 字节,共 4 个字段:
struct udphdr {
__u16 source; // 源端口号,16位
__u16 dest; // 目的端口号,16位
__u16 len; // UDP数据报总长度(首部+数据),16位,单位:字节
__u16 check; // 校验和,16位
};
16 位长度字段:取值范围 0~65535 字节,因此单个 UDP 数据报的最大长度为 65535 字节。减去 8 字节固定首部,单次能传输的最大应用层数据为 65527 字节;超过该大小的数据必须在应用层拆分发送。这也是 UDP “面向数据报” 的核心来源 —— 每个报文有明确的长度边界。- 16 位校验和:用于校验 UDP 数据报(首部 + 数据)在传输过程中是否出错。UDP 校验和是可选的(IPv4 场景下),如果校验失败,接收方直接丢弃报文,不会通知发送方,也不会重传 —— 这直接体现了 UDP 的不可靠特性。
补充:UDP 校验和计算时会包含一个 “伪首部”(源 IP、目的 IP、协议号、UDP 长度),确保数据不会传错主机和协议。
2. UDP 核心特性
- 无连接:发送数据前不需要建立连接,直接发送即可
- 不可靠:不保证送达、不保证顺序、不保证不重复,没有确认应答和重传机制
- 面向数据报:每个 UDP 报文是独立的消息,有明确边界,接收方整条读取
- 支持全双工:同一 socket 可以同时发送和接收数据
3. UDP 的缓冲区模型
UDP 在内核中只有接收缓冲区有实际的流量控制意义,没有用于重传的发送缓存:
- 发送方向:应用层调用sendto/write时,数据从用户空间拷贝到内核的 UDP 发送缓冲区,内核立刻封装 UDP+IP 首部,交给网络层发送。发送完成后内核直接丢弃报文副本,不会留存等待确认 —— 因为 UDP 不需要重传。
注意:这里的发送缓冲区仅用于暂存待发送数据,不是完全不存在;和 TCP 发送缓冲区的核心区别是:不维护已发送、未确认的报文。
- 接收方向:内核将收到的 UDP 报文按顺序放入接收缓冲区,应用层调用recvfrom/read时,从缓冲区中整条取出一个数据报交付给应用。如果缓冲区满,新到达的 UDP 报文会被直接丢弃。
4. 封装、解包与分用的底层实现
(1)基于固定首部的封装与解包
UDP 首部长度固定为 8 字节,因此处理逻辑非常简单:
- 封装:在应用层数据前面,拼接 8 字节的 UDP 首部即可。
- 解包:从数据开头读取 8 字节首部,剩下的就是完整的应用层数据。
- 分用:根据目的端口号,把数据投递到对应 socket 的接收缓冲区。
(2)内核中的实现:sk_buff 结构体

Linux 内核中,所有网络报文都通过sk_buff(socket buffer)结构体管理,它通过指针移动实现零拷贝的封装与解封装,核心指针有 4 个:
- head / end:指向整个内存缓冲区的首尾边界
- data / tail:指向当前协议层有效数据的首尾
封装时,data指针向head方向移动,留出首部空间后填入对应协议头;解包时,data指针向tail方向移动,跳过已处理的首部。整个过程不需要拷贝数据,仅通过指针偏移完成,效率极高。
5. 为什么 UDP 没有 “粘包问题”
粘包问题的本质是:接收方无法从字节流中区分出独立的消息边界。 UDP 不存在粘包,核心原因是它是面向数据报的交付语义:内核每次向应用层交付的都是一个完整的 UDP 报文,消息边界由长度字段和内核的报文拆分保证。
对比:TCP 是面向字节流的,内核只负责按字节顺序交付,不关心业务消息的边界,因此才会出现粘包 / 拆包问题,需要应用层自己定义消息格式(长度字段、分隔符等)来划分边界。
三、TCP 协议:面向字节流的可靠传输
TCP 比 UDP 复杂得多,它所有的设计都是为了在不可靠的 IP 网络上,实现可靠、有序、流量可控的字节流传输。
1. TCP 报文首部结构
TCP 首部最小 20 字节,最大 60 字节(含选项字段),核心字段如下:
16位源端口号 | 16位目的端口号
32位序号(Sequence Number)
32位确认序号(Acknowledgment Number)
4位首部长度 | 6位保留 | 6位标志位 | 16位窗口大小
16位校验和 | 16位紧急指针
选项字段(最多40字节)

关键字段详解:
4 位首部长度(数据偏移) 单位是 4 字节,取值范围 5~15。最小值 5 对应 5×4=20 字节(固定首部,无选项),最大值 15 对应 15×4=60 字节。首部长度不固定,是因为存在选项字段(如 MSS、时间戳、SACK 等)。
32 位序号与 32 位确认序号 这是 TCP 可靠传输的核心,后面单独展开讲解。
16 位窗口大小 接收方通过该字段告知发送方自己接收缓冲区的剩余可用空间,单位为字节。发送方据此控制发送速率,避免发送过快导致接收方处理不过来,是流量控制的核心。
6 位标志位 用来标识 TCP 报文的类型,控制连接的建立、关闭、数据传输等状态,后面逐一讲解。
16 位校验和 强制校验,计算范围包含 TCP 首部、数据和伪首部,校验失败直接丢弃。
16 位紧急指针 配合 URG 标志位使用,指向紧急数据的最后一个字节的序号,紧急数据长度不固定,并非只有 1 字节。
2. 可靠传输的核心:序号与确认应答机制
TCP 是面向字节流的,它给传输的每一个字节都编了序号,这就是序号(Sequence Number)。
- 序号的初始值(ISN,初始序列号)是随机生成的,目的是防止序列号预测攻击,和缓冲区地址无关。
- 确认序号(ACK Number):表示接收方期望收到的下一个字节的序号,同时代表 “该确认序号之前的所有字节都已正确接收”。
举个例子: 发送方发送了序号 1000~1999 的 1000 字节数据,接收方收到后,会回复确认序号 2001,表示 “1~2000 字节我都收到了,下次从 2001 开始发”。
捎带应答
TCP 的确认应答不需要单独发一个空 ACK 报文,可以和数据一起发送 —— 也就是把确认序号放在携带数据的 TCP 报文中一起发给对方,这就是捎带应答,能提升网络利用率。
注意:不需要对 “应答报文” 再做应答,否则会陷入无限循环。
3. 连接管理:三次握手

TCP 是面向连接的协议,通信前必须通过三次握手建立连接。
(1)三次握手的完整过程
补充:accept() 函数的作用,是从全连接队列(已经完成三次握手的连接)中取出一个连接,交给应用层处理;也就是说,三次握手在accept()返回前就已经完成了。
(2)为什么必须是三次握手,不是两次?
4. 流量控制:滑动窗口

如果发一个报文等一个应答,传输效率极低。TCP 通过滑动窗口实现批量并行发送,窗口大小由接收方的接收能力决定。
核心原理
发送方维护两个边界指针:
- 左边界:已发送且已确认的最后一个字节的下一位
- 右边界:已发送但未确认的最后一个字节 + 可继续发送的字节数
窗口大小 = 接收方通告的窗口大小。随着接收方不断返回确认,左指针向右滑动;右指针也跟着右移,继续发送新数据 —— 这就是滑动窗口。
- 窗口内的数据可以连续发送,不需要等前一个的应答
- 窗口大小由接收方动态调整,接收方缓冲区满了就把窗口设为 0,发送方停止发送
5. 丢包处理:超时重传与快重传
TCP 的可靠体现在丢包后会自动重传,有两种触发机制:
(1)超时重传
发送方发送报文后会启动超时定时器,如果在指定时间内没收到对应确认,就判定丢包,重新发送该报文。
- 超时时间不是固定的,会根据网络往返时间(RTT)动态计算(自适应重传算法),网络差则超时时间变长,网络好则变短。
- Linux 中采用指数退避策略:连续丢包时,下一次超时时间会翻倍(如第一次 500ms,第二次 1s,第三次 2s……),重试多次后判定连接异常,主动关闭连接。
(2)快重传(快速重传)
不用等超时定时器到期,通过重复 ACK 提前触发重传: 如果接收方收到了乱序的报文(比如收到了 1、2、4、5 号字节,没收到 3 号),会连续回复多个相同的确认号(都回复 3 的确认号)。发送方连续收到 3 个重复的 ACK,就判定对应报文丢了,立刻重传,不用等超时。
补充:快重传通常搭配快恢复机制,此时拥塞窗口不会降到 1,而是降到慢启动阈值,直接进入拥塞避免阶段,比超时重传的性能损失小很多。
6. 拥塞控制

滑动窗口解决了 “接收方处理不过来” 的问题,而拥塞控制解决的是 “网络中间节点处理不过来” 的问题。
TCP 引入拥塞窗口(cwnd),实际发送窗口 = min (接收方通告窗口,拥塞窗口)。拥塞控制有四个核心阶段:
- 发生超时重传:ssthresh = 当前 cwnd / 2,cwnd 重置为 1,重新进入慢启动
- 触发快重传:ssthresh = 当前 cwnd / 2,cwnd = ssthresh,进入快恢复
7. TCP 可靠性与流量控制核心总结
(1)TCP 可靠性保障体系总结
TCP 的可靠性并非由单一机制实现,而是一整套协议机制共同作用的结果 —— 它在本身不可靠的 IP 网络之上,为应用层提供了有序、无差错、不丢失、不重复的字节流交付服务。
-
基础框架:字节编号 + 累计确认 以单个字节为单位编排序号,通过累计确认机制让接收方可以按序重组数据,让发送方明确知晓已送达的数据范围,是所有可靠机制的底层基础。捎带应答则在不破坏可靠性的前提下,减少了报文数量,提升了传输效率。
-
丢包兜底:超时重传 + 快速重传 超时重传是最终兜底方案,基于动态 RTT 计算超时时间,搭配指数退避策略适配不同质量的网络环境;快速重传通过连续重复 ACK 提前触发重传,大幅降低了丢包后的等待时延,减少了超时对传输效率的冲击。
-
错误检测:强制校验和 TCP 校验和覆盖首部、数据与伪首部,能够检测传输过程中的比特翻转、数据篡改等错误。校验失败的报文会被直接丢弃,不会交付给应用层,从入口保证了数据正确性。
-
前置防护:流量控制 + 拥塞控制 分别从接收端缓冲区、网络承载能力两个维度,从根源上降低丢包概率,避免因溢出和拥塞导致的大规模丢包,是可靠性的前置保障。
补充说明:TCP 的可靠是传输层面的可靠,仅保证数据按序送达对端的内核缓冲区,不保证应用层一定会读取处理;若主机异常宕机,已接收但未被应用读取的数据依然会丢失。
(2)流量控制与拥塞控制总结
二者都是控制发送速率的核心机制,但解决的问题、作用的对象完全不同,是学习 TCP 最容易混淆的知识点,这里做明确区分与总结。
表格
| 核心目标 | 防止发送过快导致接收方缓冲区溢出 | 防止发送过快导致网络中间节点过载 |
| 作用范围 | 端到端的接收方处理能力 | 整条网络路径的承载能力 |
| 判断依据 | 接收方通告的接收窗口(rwnd) | 丢包、时延变化等网络拥塞信号 |
| 核心机制 | 滑动窗口、延迟应答 | 慢启动、拥塞避免、快重传、快恢复 |
| 窗口决定因素 | 接收方剩余缓冲区大小 | 网络当前的拥塞程度 |
TCP 实际生效的发送窗口遵循取小原则: 发送窗口大小 = min (接收通告窗口 rwnd, 拥塞窗口 cwnd)
- 当网络通畅、接收端处理能力不足时,发送窗口由接收窗口决定,流量控制起主导作用;
- 当接收端处理能力充足、网络出现拥塞时,发送窗口由拥塞窗口决定,拥塞控制起主导作用。
二者共同构成了 TCP 的自适应传输控制体系,让 TCP 能够在不同网络环境、不同对端性能下动态调整发送速率,兼顾可靠性与传输效率。
8. 连接释放:四次挥手与 TIME_WAIT

TCP 连接是全双工的,关闭需要双方各自关闭自己的发送方向,因此需要四次挥手。
(1)四次挥手完整过程
我们规定主动关闭方为 A,被动关闭方为 B:
(2)TIME_WAIT 状态的作用
MSL 是报文最大生存时间,2MSL 就是报文在网络中最长往返时间。TIME_WAIT 等待 2MSL 有两个核心作用:
(3)端口复用:解决 TIME_WAIT 导致的绑定失败
如果服务端作为主动关闭方,重启后会因为 TIME_WAIT 状态无法立刻绑定原来的端口。可以通过setsockopt设置SO_REUSEADDR选项,允许端口复用:
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
// 示例:设置端口复用
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
补充:还有SO_REUSEPORT选项,允许多个进程绑定同一个端口,用于负载均衡场景,和SO_REUSEADDR作用不同。
9. 其他标志位与机制
RST:复位连接 用于异常场景:比如向一个未建立连接的端口发数据,对端会回复 RST 报文,直接重置连接。常见于端口未开放、连接异常中断等场景。
URG:紧急标志 标识报文中有紧急数据,配合紧急指针字段使用,紧急数据会被优先处理,不需要按顺序排队。实际应用中使用较少。
PSH:推送标志 提示接收方操作系统尽快把数据从内核缓冲区交付给应用层,不要等缓冲区攒满再上交。常用于交互性强的场景(如远程登录)。
延迟确认(Delayed ACK) 接收方收到数据后不会立刻回 ACK,而是稍等一小段时间(Linux 通常 40~200ms),如果这段时间内有数据要发给对方,就捎带 ACK 一起发,减少报文数量。延迟时间不能太长,否则会触发发送方超时重传。
10. TCP 异常场景处理
- 如果后续有数据发送:发送后收不到应答,超时重传多次后会判定连接失效,关闭连接。
- 如果没有数据交互:连接会一直处于半打开状态。TCP 自带保活机制(Keepalive),开启后长时间无数据交互时,会定时发送探测报文,无应答则关闭连接。
注意:TCP 保活默认关闭,且默认间隔长达 2 小时,实际业务中通常在应用层自己实现心跳机制




