TCP的确认应答机制(Acknowledgement Mechanism)是其提供可靠,有序、无重复传输的核心机制之一。它通过接收方对已成功接受的数据进行确认,使发送方能够判断哪些数据已经被正确接收、哪些需要重传。
一、确认应答机制的核心原理及过程
TCP 报头结构中的 序号(Sequence Number)、确认序号(Acknowledgment Number)和 ACK 标志位是实现此机制的关键字段 。
1.序列号:发送方位发送的每一个字节数据按顺序编号。一个 TCP 报文段的首字节序列号,就是该报文段的序列号
2.确认应答号:接收方在 ACK 报文中携带,其值为 "期望收到的下一个字节的序列号" 。这个数字隐式地却确认了该序号之前的所有数据都已正确接收
3.ACK标志位:ACK 标志位是 TCP 报文头部中一个 1 比特地标志位,用于明确地告知对方:"我报文中的确认号字段是有效的,简单的说 ACK = 1 表示 "我正在确认数据 ", ACK = 0 表示 "我没有在确认数据"
1、数据发送与序列号
发送方在发送数据时,会为每个字节的数据分配一个唯一的序列号。
TCP 报文是以 "段" 为单位发送的。每个 TCP 报文头中都会包含 SEQ 字段, 其值为该报文段第一个字节的序列号
2、接受确认与确认号
接收方成功收到一个数据段后,会发送一个 ACK 报文来确认。
这个 ACK 报文头中:ACK 标志位被置为 1、包含一个 ACK 字段,其值 = 期望收到的下一个字节的序列号。这相当于告诉发送方:"在这个编号之前的所有字节我都已经收到了,请下次从这个编号开始发"。
3、发送方收到确认后的行为:
发送方收到 ACK 后,就知道 ACK 编号之前的所有数据都已经被接收方成功接收
它会根据确认号,将已确认的数据从 "发送缓冲区" 中清除
如果还有未确认的数据,或者有新的应用层数据要发送,则继续放松下一个数据段。
二、简单示例:
场景设定
-
客户端 (Client):发送一个简短的请求 "Hi"
-
服务器 (Server):接收并确认
1、三次握手后,双方状态:
三次握手过程:
-
客户端 → 服务器:SYN, SEQ=1000
-
服务器 → 客户端:SYN+ACK, SEQ=5000, ACK=1001
-
客户端 → 服务器:ACK, SEQ=1001, ACK=5001
握手完成后:
-
客户端:下一个要发送的数据从 SEQ = 1001 开始
-
服务器:下一个要发送的数据从 SEQ = 5001 开始
-
客户端已确认服务器的SYN: ACK = 5001
-
服务其已确认客户端的SYN: ACK = 1001
2、客户端发送数据(包含"Hi")
数据报文: 客户端 -> 服务器
TCP头部详情:
序列号 SEQ: 1001 (这是客户端数据的第一个字节)
确认号 ACK: 5001 (确认握手,期望收到服务器的第5001字节)
标志位: ACK=1, PSH=1 (确认有效 + 推送数据)
数据: "Hi"(2个字节,ASCII: 0x48 0x69)
3、服务器接受并发送 ACK 确认
服务器收到数据后:
检查 SEQ = 1001,正式期望的值
接受 2 字节数据 "Hi"
立即(或稍后)发送确认
ACK 确认报文:服务器 -> 客户端
TCP头部详情:
-
序列号 SEQ: 5001 (服务器自身的序列号,未发送数据所以不变)
-
确认号 ACK: 1003 (关键!1001 + 2 = 1003)
-
标志位: ACK=1 (纯确认)
-
窗口: 65535
-
数据: 无(纯ACK报文没有数据)
三、超时重传
1、为什么需要超时重传
网络是不可靠的,两种可能的错误:
**数据包丢失:**发送的数据包在传输中丢失
**ACK 丢失:**接收方的确认包在返回途中丢失
2、超时重传如何解决这两种超时情况
-
每个数据包有自己的RTO计时器(发送方在发送一个数据段后,等待对应ACK的最长时间。如果超过这个时间还没收到ACK,就触发重传)
-
RTO是动态计算的,基于网络状况
-
太长 → 丢失恢复慢
-
太短 → 虚假重传,浪费带宽
对于上述两种情况,发送方要是没收到接收方返回的 ACK确认报,超过一定时间,就会重新发送一个相同的数据包。但是超时重传不是无限次的! TCP有明确的重传次数限制,达到上限后会放弃重传并关闭连接。
| 方面 | 数据包丢失 | ACK丢失 |
|---|---|---|
| 发生位置 | 发送方→接收方方向 | 接收方→发送方方向 |
| 根本原因 | 数据包本身丢失 | 确认包丢失 |
| 接收方状态 | 从未收到数据 | 已收到数据,并已发送ACK |
| 发送方视角 | 数据未确认 | 数据未确认(实际已送达) |
| 最终结果 | 都会触发重传 | 都会触发重传 |
对于第一种情况,可以通过再发送一遍数据包来解决,但是对于第二种情况,万一这个数据的请求时扣款请求,就会发送额外扣款的错误
那么 TCP 是如何解决上述问题的呢?
TCP 通过接收缓冲区来解决以上问题
接收缓冲区
接收缓冲区的定义:接收缓冲区 是操作系统内核为每个TCP连接分配的一块内存区域,专门用于临时存储从网络到达、但尚未被应用程序读取的数据。
接收缓存区的作用:当主机 A 发送数据到达主机B 时,是先到达主机 B 内核中一个名叫接收缓冲区的空间当中。只有当应用程序调用 read/Scanner.next 的时候,才可以从接收缓冲区中读取数据
所谓的读操作本质就是对接收缓冲区的读取
当数据到达主机 B 的接收缓存区的时候,会先对数据进行一个判断
要是这个数据目前仍存在于接收缓冲区或者曾经在接收缓冲区中存在过,则直接把这个数据丢掉此时这个接收缓冲区就可以保证应用程序在调用 read() / Scanner.next 操作的时候,不会读取重复的数据,解决了丢包的问题,避免了应用程序出现二次扣款的操作
那么接收缓冲区是如何判断数据重复的
由于发送方位发送的每一个字节数据按顺序编号。一个 TCP 报文段的首字节序列号,就是该报文段的序列号。
针对以上两种情况
1、旧的数据还在缓冲区,还没被应用程序读走,此时可以根据新数据报头序列号的值来判断是否和旧数据的报头序列号相同来判断是否是重复数据
2、旧的数据不在缓冲区,已经被应用程序读走,此时在缓冲区已经无法查到这个数据了
面对第二种情况,接收缓冲区可以看成一个以序号为优先级的堵塞队列
在这个优先级逻辑下:当所有数据到达缓冲区之后,会在这个缓冲区里按照序号,从小到大排序,这样可以解决先发后至的问题(由于网络路径的不确定性,先发送的数据包比后发送的数据包更晚到达接收方),所以应用程序在缓冲区里进行读取操作也是按序读取的
而且应用程序在读取问数据之后, TCP 的 API 会记录号应用程序读取的最后一个数据的序号,此时这个 TCP API 记录的最后一个序号就可以解决旧的数据不在缓冲区的情况
注意:在超时重传机制中,即使接收方缓冲区发现传输的是一个重复的数据,进行丢弃之后,也还是要对发送方返回一个 ACK应答报文的,如果不返回 ACK,发送方还会再一次发送数据。