
📚 本文收录于「流浪」的系列专栏
| 🐧 Linux系统 | ⚙️ C++ |
| 📊 数据结构与算法 | 🐍 Python |
| 🔗 LangChain & LangGraph | 🗄️ MySQL 数据库 |
| 🌿 Git 工具 | 🌐 计算机网络 |
| 🤖 LLM | 💯 大厂面试、八股 |
| 📚 学习筑基专栏 |
🏠 博客主页:流浪 | 📝 原创首发于 CSDN
前言: 第15篇用 4 位首部长度 切出可变报头,确认应答与序号 撑起可靠性。本篇接着拆剩下的属性字段:16 位窗口大小 怎么搭应答便车做流量控制 ,六个标志位 怎么给报文分型,URG 与紧急指针怎么送一字节加急。

一、16 位窗口大小:捎带在应答里的流量控制
1.1 接收缓冲区满了怎么办
1. 丢弃报文再等重传不合理
- TCP 通信的本质是往对方的接收缓冲区里拷贝数据:发送方的数据先落自己的发送缓冲区,内核搬到网络,对端收下后先塞进接收缓冲区,最后才轮到应用程序 read 走
- 那接收缓冲区要是满了呢?直觉答案是新到的报文直接丢弃,发送方等超时重传
- 但重传的数据到达时,对方的缓冲区多半还是满的------继续丢、继续等、继续重传,带宽和 CPU 全耗在注定收不下的发送上,同时耗费了大量的算力资源。所以这个设计不合理
2. 应答报文里捎带剩余空间
- TCP 的基本盘是发送方每发一条消息,接收方都要回一个应答,这个动作什么时候都免不了
- 既然应答横竖要发,接收方就在应答报文里顺手多捎一个数字:自己接收缓冲区当前的剩余空间还有多少
- 发送方拿到这个数字,就知道对方现在吃得下多少数据,据此控制接下来的发送频率,不至于把对方灌爆------这套机制就是流量控制(flow control)
1.2 窗口大小字段填的是什么
1. 填的是自己的接收能力
- 16 位窗口大小字段,填的永远是「我的接收缓冲区还剩多少空间」,单位是字节
- 我给对方发应答,窗口填我的剩余空间;反过来对方给我发应答,填的就是对方的剩余空间
- 每个方向各自报告各自的接收能力,谁发应答谁报告,双向对称
窗口大小的本质,是接收方搭应答的便车报告自己的接收能力,发送方据此收着发------流量控制不靠丢包惩罚,靠提前报数。
二、六个标志位:给报文分型的六个比特

2.1 为什么要有标志位
1. 报文类型不同,处理动作就不同
- 同样是从网络收到一封 TCP 报文,有的在请求建连、有的在确认数据、有的在通知关连接、有的在报告异常
- 接收方不可能用同一套动作应付所有报文,针对不同类型的报文,接收方要有不同的做法
- 所以报头里必须有一块地方写清「这封报文是什么类型」,这就是标志位存在的意义
2. 保留字段辟出六个比特
- 实现方式是采用保留字段的方法,在报头中开辟一段固定大小的比特位给标志位使用
- 从 C 语言的角度看,TCP 报头就是一个位段结构体,六个标志位是六个一比特的成员,置 1 表示生效、置 0 表示无效
- 《计算机网络第14:传输层------UDP 协议:8 字节定长报头划出报文边界,16 位端口号完成分用》 的 8 字节报头里没有任何标志位------UDP 不建连、不应答、不关连接,报文只有承载数据一种类型,自然不需要分型
2.2 SYN 与 ACK:握手的三次收发
1. SYN 发起第一次握手
- SYN(synchronize,同步标志位)在建立连接的握手过程中使用,是整个 TCP 通信的起点
- 拿客户端 C 访问服务端 S 来说:《计算机网络篇7:传输层------TCP 网络编程常用接口与调用流程》 里 connect 一声令下,C 向 S 发出 SYN 置 1 的报文------「我想和你建立连接」
- 第一次建立连接不一定能成功,握手全程都存在失败的可能
2. SYN 与 ACK 回应第二次握手
- S 收到后回一封 SYN、ACK 同时置 1 的报文,这就是第二次握手:确认你的请求,也表达我自己的建连意愿
- 前两次握手不能携带数据:三次握手没有完成,连接尚不存在,数据无处安放,报文里只有 TCP 报头
- 更严格的口径是 SYN 报文虽不带数据,但要消耗一个序号,好让后续的确认号有据可依
3. ACK 收尾第三次握手
- C 再向 S 发一封 ACK 置 1 的报文,这是第三次握手,三次握手到这里才算完成,双方才能正式收发数据
- ACK 标志位的职责只有一件事:声明本报文的确认号有效------《计算机网络第15:传输层------TCP 报头:4 位首部长度切出可变报头,确认应答与序号撑起可靠性》 已把确认应答与序号讲透,此处不展开
- 三次握手、四次挥手的完整流程同样留在后续专门篇,本篇只盯标志位在各步骤里分别干什么

2.3 RST:握手失败的复位信号
1. C 与 S 的成功点差一拍
- C 发出第三次握手的 ACK 之后,并不知道这封报文有没有被 S 收到------落没落地,C 说了不算
- 站在 C 角度:第二次发送(发出第三次握手的 ACK)即认为三次握手成功,连接已经建立
- 站在 S 角度:第二次收到报文(收到 C 的 ACK)才算三次握手成功,在此之前连接始终没建立
- C 的成功点比 S 早一拍,两个判定点之间隔着一封报文穿越网络的时间差
2. 时间差内丢包触发复位
- 在这个时间差内,第三次握手的 ACK 是有可能丢失的
- 丢失后局面就拧了:C 认为握手已成功,接着发正式数据;S 认为握手没完成,收到的是一封不属于任何已建立连接的报文
- S 的应对是回一封 RST(reset,复位标志位)置 1 的报文给 C:连接失败,请重新建立连接
- RST 的本质就一句话------报文到达时找不到它所属的连接,接收方就地复位重来
RST 不是哪一方犯了错,而是「双方对连接成立的判定点天然差一拍」这个时间差的兜底方案。
2.4 FIN 与 PSH:关连接与催收数据
1. FIN 通知对方关闭连接
- FIN(finish)的作用是通知对方「我这边需要关闭连接了」,它出现在四次挥手的过程中
- 与握手同理,四次挥手的完整流程留给专门篇,这里只需认下 FIN 是挥手的信号
2. PSH 立刻发送、立刻上交
- 通俗口径:PSH(push)提示接收端的应用程序立刻从 TCP 接收缓冲区把数据读走,别让数据躺着
- 实际语义更双向一些:发送方置 PSH 是催 内核别攒了,把发送缓冲区里现有的数据立刻 flush 出去;接收方 TCP 收到后也尽快把接收缓冲区的数据上交应用层,而不是等缓冲区攒满(真正决定接收方接收缓冲区的数据被不被拿走的是接收方的上层,PSH在这里只是提醒,它并不能决定)
- 最典型的受益场景是交互式程序:终端里敲一条命令要立刻看到回显,靠的就是 PSH 推动的「立刻发送、立刻上交」
三、URG 与紧急指针:绕过接收队列的加急数据
3.1 为什么要紧急数据
1. 字节流按序排队的代价
- TCP 保证可靠传输,这保证了数据按序列到达,接收端维护着一条字节流式的接收队列------先发的排前面,后发的排后面,应用程序读到的永远是排好序的流
- 这个设计对普通数据很友好,代价是任何数据都得排队,没有加急通道
- 如果有一段数据想被优先读取,靠正常排队是等不到的
2. 下载中途取消信号排在几万个字节后面
- 情景:一条 TCP 连接正在传一个大文件,接收队列里排着几万个字节的文件数据,等应用程序慢慢读
- 此刻你反悔了,想立刻取消这次下载------取消命令本身也是一份数据,正常排队要排在几万个字节后面,等轮到它文件早传完了,取消已经失去意义
- 这就需要有一份数据能绕过接收队列直达对端,这就是紧急数据(urgent data)
3.2 紧急指针的机制与现状
1. URG 声明、指针定位
- URG 置 1 表示本报文的紧急指针有效,置 0 表示无效,接收方不用理会
- 紧急指针是一个 16 位的偏移量:当前报文的有效载荷中,这个偏移量处就是紧急数据
- 紧急数据只有一个字节------恰好对应情景里「取消」这一个动作,一个字节够用
2. RFC 口径与已废弃的现状
- 更严格的口径来自 RFC 6093:紧急指针指向紧急数据末尾的位置,且是相对本报文段序列号的偏移
- 两条口径描述的是同一个机制,差别只在偏移指向的基准点------课堂口径指到紧急数据处,RFC 口径指到紧急数据的末尾
- 现代实践里这套机制已基本废弃:RFC 6093 明确判定它不适合继续使用,主流系统仅为兼容保留处理逻辑,应用程序层面极少再用
紧急机制解决的是「字节流里插队」,但一个字节的加急通道实在太小,最终成了 TCP 报头里的历史包袱。
四、全篇总结
1. 窗口大小撑起流量控制
- 应答报文顺手捎带接收缓冲区剩余空间,发送方据此控制发送频率
- 双向对称:谁发应答,谁报告自己的接收能力
2. 标志位给报文分型
- 保留字段辟出六个比特,置 1 生效:URG/ACK/PSH/RST/SYN/FIN
- SYN 与 ACK 管握手,RST 管复位,FIN 管关闭,PSH 管催收
3. URG 送一字节加急
- URG 声明有效,紧急指针以偏移量定位一个字节的紧急数据
- 紧急数据绕过接收队列优先上交,现代实践已废弃
五、文末面试题
5.1 推导题
1. 接收缓冲区满了丢弃报文等重传,为什么不合理?流量控制怎么绕开它?
答(推导):丢弃加重传意味着发送方在明知对方收不下的情况下继续硬发,重传数据到达时对方缓冲区多半还是满的,于是丢、等、重传循环往复,带宽和 CPU 全耗在注定失败的发送上。而TCP的请求必有应答机制可以实现流量控制。流量控制的绕法是不让这种局面发生:接收方在应答报文的窗口大小字段里顺手报告剩余空间,发送方提前知道对方吃多少,从源头控制发送节奏。
2. 站在 C 和 S 的角度,三次握手分别在哪一步算成功?这个时间差为什么引出 RST?
答(推导):C 发出第三次握手的 ACK 即认为成功,S 收到这封 ACK 才算成功,C 的判定点比 S 早一拍。时间差内 ACK 丢失后,C 已认连接建立并发数据,S 却收到一封不属于任何已建立连接的报文,只能回 RST 要求重新建连。RST 的本质就是报文与连接对不上号时接收方的复位反应。
3. 前两次握手为什么不能携带数据?
答(推导):前两次握手发生时三次握手尚未完成,连接本身还不存在,数据没有归属的连接可以依托,所以报文只有 TCP 报头。第三次握手时发起方已收到过对端的 SYN+ACK,本端连接资源就绪,ACK 报文段可以顺带携带数据。
5.2 真题
1. TCP 六个标志位各自的作用是什么?
答(推导 · 已对照解析,转述):URG 表示紧急指针有效;ACK 表示确认号有效,携带 ACK 的报文段叫确认报文段;PSH 提示接收端应用程序立刻从 TCP 接收缓冲区读走数据;RST 表示要求对方重新建立连接,对应复位报文段;SYN 用于请求建立连接,对应同步报文段;FIN 通知对方本端要关闭连接,对应结束报文段。
【真题·转述自 牛客网面经帖】
2. 16 位窗口大小字段的作用是什么?
答(推导 · 已对照解析,转述):窗口大小是 TCP 流量控制的手段,指接收通告窗口,它告诉对方本端 TCP 接收缓冲区还能容纳多少字节的数据,对方据此控制发送数据的速度;窗口值随缓冲区剩余空间动态变化。
【真题·转述自 牛客网面经帖】
3. RST 标志位什么时候出现?
答(推导 · 已对照解析,转述):RST 置 1 表示连接出现严重差错、需要重新建立连接,典型场景是报文到达时找不到所属的连接------握手未完成却收到数据、访问未被监听的端口、对端连接早已不存在。它还用于拒绝非法报文段和连接请求。
【真题·转述自 牛客博客】
4. TCP 紧急指针指向哪里?
答(推导 · 已对照解析,转述):紧急指针是一个正偏移量,与序号字段的值相加表示紧急数据末尾的序号------它只在 URG 标志为 1 时有效。注意口径有分歧:RFC 6093 定义为指向紧急数据末尾,也有资料表述为指向最后一个紧急数据的下一字节,答题时点出「偏移量 + 相对序号」这两个要素即可。
【真题·转述自 牛客网面经帖】
5. PSH 置 1 时接收方应该做什么?
答(推导 · 已对照解析,转述):PSH 是推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队;对发送方而言则是催促内核立刻把发送缓冲区现有数据发出去。交互式终端的即时回显是典型应用。
【真题·转述自 牛客网面经帖】
6. URG 标志位表示什么?紧急数据有多大?
答(推导 · 已对照解析,转述):URG 表示本报文段的紧急指针是否有效,为 1 时紧急指针生效,配合紧急指针一起使用;紧急数据只有一个字节,作用是绕过字节流接收队列优先上交应用层,现代实践中该机制已被废弃。
【真题·转述自 牛客网面经帖】
💬 结语: 窗口大小报的是接收能力,标志位分的是报文类型,URG 送的是一字节加急------TCP 报头的属性字段到此拆完。评论区聊聊你抓包时见过哪些标志位组合,觉得有用点个赞。