1、保证报头和有效载荷分离的问题
4位首部长度
2、如何保证有效载荷数据部分应该交付给上一层的那一层协议呢?
答:16位目的端口号
1. 如何保证报头和有效载荷分离?(关于"4位首部长度")
在 IPv4 协议 中,正是通过首部中的 "4位首部长度"(Internet Header Length, IHL) 字段来保证分离的。它的工作机制如下:
-
单位换算 :这个4位字段的长度单位是 4字节(32位) 。它能表示的范围是 0~15,换算成字节数就是
0 ~ 60字节。 -
固定与可变 :标准的IPv4首部固定长度为20字节(即该字段值为
5,因为5 × 4 = 20)。如果包含"选项"字段,首部会变长(最大60字节)。 -
分离逻辑(接收端操作):
-
网卡收到数据包后,IP协议栈首先读取该4位字段的值(比如读取到
6)。 -
将其乘以4,计算出报头的实际字节数(
6 × 4 = 24字节)。 -
总长度字段(IPv4首部另一个字段)指明了整个IP数据包的总长度。
-
用 总长度 - 首部长度,就能精准地计算出有效载荷(Payload)的起始位置和长度。
-
-
异常保证 :协议规定,如果该字段值小于
5(即首部小于20字节)或大于15(超过60字节),接收方会认为该数据包损坏,直接丢弃,不会进行后续解析,从而在物理层面杜绝了因首部长度错误导致载荷读取越界的问题。
2. 如何保证有效载荷交付给上一层的那一层协议?(关于"16位目的端口号")
你给出的答案"16位目的端口号"非常精准,但需要补充一个细节:IP层和传输层分工不同。
为了让你彻底明白,我们分两步看:
-
第一步(IP层):区分上一层是TCP还是UDP
-
这一步不靠 端口号,而是靠IP首部中的 "8位协议号"。
-
接收端的IP层收到包后,查看该字段(例如
6代表TCP,17代表UDP,1代表ICMP),然后将剥离了IP首部的"有效载荷"交给操作系统内核中对应的传输层协议模块(TCP模块或UDP模块)。
-
-
第二步(传输层):区分具体交给哪一个应用程序(关键点)
-
当TCP或UDP模块拿到数据后,此时才轮到 "16位目的端口号" 发挥作用。
-
操作系统内核维护着一张 "端口号 ↔ 应用程序/进程" 的映射表(即套接字Socket绑定关系)。
-
TCP/UDP模块读取该16位目的端口号(例如
80、443或3306),查询映射表,找到正在该端口上监听的特定进程(如Nginx、MySQL客户端)。 -
最终,传输层将剩余的有效载荷精准地投递到该进程的接收缓冲区中。
-
PSH标志位
PSH提示接收端应用程序立刻从TCP缓冲区把数据读走 (PUSH)
-
-
发送端 :当应用调用
send()或等效方法并设置 PSH 标志(或由 TCP 栈自动设置,通常当发送缓冲区为空时)时,TCP 会立即构造并发送该报文段,而不等待更多数据填入缓冲区(Nagle 算法可能会抑制此操作,但 PSH 会覆盖它)。 -
接收端 :当接收到带有 PSH=1 的报文段时,接收端 TCP 栈不会将其放入乱序队列等待更多分段以填满 MSS(最大报文段长度)或接收窗口。它会立即将目前为止接收到的所有缓冲数据向上推送到应用层(套接字接收缓冲区),实质上触发了应用的
read()调用返回。 -
重要提示:在现代操作系统中,PSH 标志很大程度上是"建议性"或"历史性"的。由于现代 TCP 栈使用快速路径和即时 ACK,PSH 的效果不如早期明显。然而,它本质上是对接收方"立即交付"的指令。
-
URG标志位
作用:紧急指针是否有效
我们可以理解紧急指针的位置,当作应用层缓冲区中的某一个偏移量。

1. 工作原理(发送端与接收端逻辑)
-
发送端设置:
-
应用层调用
send(..., MSG_OOB)(带外数据)发送紧急字节。 -
TCP 协议栈将 URG 标志置 1,并计算偏移量。
-
关键计算:紧急指针值 = (紧急数据最后一个字节的序列号 - 当前报文段起始序列号)+ 1。
-
举个例子:当前报文段起始序列号(Seq)= 100,你要紧急发送的字节是
'A'(占 Seq=100)。那么紧急指针的值就是1(100 - 100 + 1 = 1),表示从 Seq=100 开始的 1 个字节 是紧急的。 -
如果紧急数据持续到 Seq=105,指针值就是
6,代表 Seq=100~105 这 6 个字节是紧急的。
-
-
接收端处理:
-
网卡收到 URG=1 的报文段后,TCP 协议栈提取紧急指针值,计算出紧急数据的"终点边界"。
-
进入"紧急模式" :接收端 TCP 不再按顺序把数据递给应用,而是绕过接收缓冲区排队 ,直接把指针标记的那段数据(带外数据 OOB)推给应用进程,并发送 SIGURG 信号 (或通过
select/epoll异常事件)通知应用程序"紧急数据到了"。 -
应用程序必须通过特殊的
recv(..., MSG_OOB)来读取这段数据,不能用普通的read()。
-
2. 一个极其重要的"伪命题":紧急指针 ≠ 高优先级传输
很多人以为设了 URG 和紧急指针,数据就能"插队"更快到达对方。这是错的!
-
在网络传输队列中,带 URG 标志的报文没有任何优先转发权(路由器不看这个标志)。
-
它的"紧急"仅仅体现在接收端的处理策略 上:即到达接收端后,不排队等待后续乱序数据,直接呈递给应用层。
3. 现实中的残酷真相:现代编程几乎不用它
虽然这是 TCP 标准里的字段,但在今天的互联网开发中(尤其是 HTTP/HTTPS、微服务),16位紧急指针几乎没有用武之地,原因有三:
-
仅支持 1 个字节的带外数据(OOB) :TCP 标准规定紧急指针虽然能划一片区域,但实际可靠的带外数据(紧急数据)通常只保证传输 1 个字节。如果你想发一串字符串作为"紧急命令",根本传不完。
-
极易引发死锁 :如果紧急数据到达,但接收方应用进程忙于处理其他逻辑,长期不调用
MSG_OOB读取,该紧急数据会一直占着连接状态,后续正常数据会被阻塞。 -
安全风险 :历史上著名的 Ping of Death 变种和某些老版本 Linux 内核漏洞,就是利用伪造的紧急指针(指向不存在的序列号)造成内核崩溃或缓冲区溢出。
为什么会有紧急数据呢?
答:
核心原因 :为了解决 TCP"按序交付"导致的队头阻塞 ,让控制指令(如 Ctrl+C 中断、FTP 中止)能绕过排队的大数据流,直接被接收端优先处理。
历史用途:Telnet 远程中断、FTP 文件传输终止。
确认应答机制


收到应答是对历史报文的可靠性保证,也就是在如上图的主机A收到第一次应答,是确定了数据(1~1000)已经发给主机B了。
超时重传机制

主机A发送数据给B之后, 可能因为⽹络拥堵等原因, 数据⽆法到达主机B;
如果主机A在⼀个特定时间间隔内没有收到B发来的确认应答, 就会进⾏重发;
但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了;

因此主机B会收到很多重复数据。那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。
这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。
那么,超时的时间如何确定?
最理想的情况下,找到一个最小的时间,保证"确认应答一定能在这个时间内返回"。但是这个时间的长短,随着网络环境的不同,是有差异的。如果超时时间设的太短,会影响整体的重传效率;如果超时时间设的太短,有可能会频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后,仍然得不到应答,等待2*500ms后再进行重传。如果仍然得不到应答,等待4*500ms进行重传。依次类推,以指数形式递增。累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。
连接管理机制

accept不参与三次握手。双方close是在进行回收过程。
传的就是这个:

结论1:三次握手,每一次都是 从"发出"开始算的。双方TCP,是自主完成的。
为什么是三次握手?
答:1、为通信前,先建立信道,---阻碍通信情况(1、双方意愿2、网络不通畅)
验证全双工通信,以最小的成本验证网络通信问题。
(双方得到了彼此的许诺)
2、验证双方通信意愿问题
四次挥手
1、四次挥手,双方都可以作为主动断开连接的一方。
2、一方断开连接的本质是: 一方不写了。不写数据,不认为把链接结构体管理fd释放了。
3、断开连接,不一定是彻底释放,不写用户数据,不代表不发送报头和标志位。
局部关闭fd的系统调用

listen :
#include <sys/socket.h>
int listen(int sockfd, int backlog);
没有accept,链接能够建立成功;接受端维持的暂时不用accept到应用的链接个数是有上限的。
backlog+1