TCP核心机制:确认应答
文章目录
- TCP核心机制:确认应答
- 观前提醒:
- [1. 确认应答](#1. 确认应答)
-
- [1.1 举例(给你的女神发消息)](#1.1 举例(给你的女神发消息))
-
- [为什么会出现 后发先至 的问题?](#为什么会出现 后发先至 的问题?)
- 如何解决后发先至?(编号)
- [1.2 32位序号 & 32位确认序号](#1.2 32位序号 & 32位确认序号)
-
- [六位标志位中的 ack 位](#六位标志位中的 ack 位)
- 问题:应答报文,响应?
- [1.3 编写序号规则:](#1.3 编写序号规则:)
-
- [32位序号 编写序号规则](#32位序号 编写序号规则)
- [32位确认序号 的编写序号规则](#32位确认序号 的编写序号规则)
- [1.4 接收缓冲区](#1.4 接收缓冲区)
- [1.5 需要注意的两个点:](#1.5 需要注意的两个点:)
- [1.6 可能会疑惑的地方:](#1.6 可能会疑惑的地方:)
- [1.7 总结:](#1.7 总结:)
观前提醒:
如果你是第一次点击这篇博客,需要你点击这篇博客的链接:
网络原理(12):传输层 -- TCP协议的报文格式 & 十大核心机制的博客目录
了解这篇博客的内容之后,再回来看这篇博客。
此处,我们讲到的 "TCP数据报的发送",例如:发送 ack(应答报文)
一定是要通过 IP协议封装(网络层),数据链路层封装,通过物理层转为光电信号,传输给对端,对端接收到光电信号,也要反着解析一遍(分用)。
关于封装和分用,可以看我的这篇博客:
Java网络初识(4):网络数据通信的基本流程 -- 封装
Java网络初识(5):网络数据通信的基本流程 -- 分用
1. 确认应答
保证 TCP协议 可靠传输的一个关键前提 ,是发送方知道自己的数据,是否被接收方收到 。
需要接收方,返回一个 "应答报文"(acknowledge,简称 ack),发送方收到应答报文,就可以确认对方是收到消息了。
我们先来举个例子,来说一下,为何需要这个机制,这个机制中,又涉及到什么东西。
1.1 举例(给你的女神发消息)
假设,你现在,和你的高中女神很熟,熟到什么程度?
朋友之上,恋人未满。
我们假设,现在的时代背景,是 199X年 或 2000年初,QQ和微信,还没有普及,使用的是手机自带的短信功能。
有一天周末,你约你女神,出来看电影。
发消息的形式,可以是这样的:

"好啊好啊" 就可以认为是一个应答报文。
这样的模型,存在明显的缺陷 :如果连续发送多条信息 ,可能会出现问题。
假如,我们连续发的是两条信息:

上面这张图片,信息发送,看似没有问题。
但是在网络上,就会有一个神奇的事情:后发先至
什么意思?
还是以上面那幅图为例,注意,这次的图,不一样的:

后发先至,如图所示。
没发生后发先至 的情况:
女神针对 "女神女神,去不去看电影" 这件事的回复是:好啊好啊
女神针对 "女神女神,做我女朋友好不好?"的回复是:滚!
发生后发先至 ,指的是接收方(我):
先接收到 的消息是:"滚" (我会认为:是女神对 "女神女神,去不去看电影?" 的回答)
后接收 到的消息是:"好啊好啊"(我会认为:是女神对 "女神女神,做我女朋友好不好?" 的回答)
这时候,我的理解,就会发生错误 :
女神,不想去看电影 ,但是,女神同意做我女朋友。
这样一来,发送方(我),对于信息的理解,就发生错误。
为什么会出现 后发先至 的问题?
现实中(接亲):
我举一个现实中的例子:
举行婚礼的时候,男方这边要接亲,派出一个车队,开到女方的家,把新娘接上,接到男方家里,开始办仪式,办酒席。
我们就以这个车队为例子,类比于我们上面的两条信息。

车队这边,什么时候,会出现 后发先至 的情况呢?
如果男方家离女方家里比较远。
路上,就会有很多的 红绿灯!
如果刚好,车队前面三辆车走了之后,转红灯了:

(假设的情况是:前面三辆车,不等后面两辆车了)
红灯一等,就是几十秒,那后面的两辆车,还怎么跟,只能开导航,根据女方家里的地址,按照路线,继续开了。
这样一来,就走散了 。
走散了,会有什么情况?
有没有这样一种可能?
前面三辆车,走的路,比较远,路上的红绿灯挺多的。
后面两辆车,跟着导航,走的是近路,一路上,没有红绿灯。
这个时候,后面两辆车,就比 头车 先到了女方家里。
问题来了,头车没到,后面这两辆车,能把新娘接走吗?
答:当然不能,那就不叫接亲,叫抢亲了。
这就是一种典型的 后发先至 的情况。
网络上:
网络上,也是一样的情况,转发数据的时候,数据都会经过路由器/交换机 ,每个路由器/交换机,就相当于十字路口 。
有些数据,在这个十字路口,可能通过的快,可能通过的慢 。
而且,不是每个数据,都是按照指定路线走的,每条路线,拥堵情况,也各不相同。
这样的情况,就会导致 后发先至 的情况了。
如何解决后发先至?(编号)

后发先至 的情况,TCP 怎么处理?
答:给传输的数据,进行编号。
编号之后,上述的信息发送,可以这么干:

这样的话,即使信息到达我这边的顺序,出现混乱 ,也没事,通过序号 ,就可以重新组织信息的发送顺序。
就可以清楚的知道,哪个回答,对应哪个问题。
1.2 32位序号 & 32位确认序号
我们之前在这篇博客:
网络原理(12):传输层 -- TCP协议的报文格式 & 十大核心机制的博客目录
中提到过,TCP协议的报文格式有:32位序号 & 32位确认号


这两个报文信息,就是针对我们的数据,进行编号。
上述,我发送给女神 的两条信息 :
"女神女神,去不去看电影"
"女神女神,做我女朋友好不好?"
就会使用 序号,进行编排。
女神发送给我 的两个信息 :
"好啊好啊"
"滚"
就会使用 确认序号,和 序号 编排的信息,一一对应 。
说明我这个应答报文(女神的回复信息),是在根据哪个数据(我发送给女神的信息),进行应答。
这是序号和确认序号的作用。
序号 ,是对任何数据包,都可以用 的。
确认序号 ,只在应答报文中,才生效。
六位标志位中的 ack 位
我们 TCP协议 中,还有 六个标志位,其中,有一个是 ack 位 。


ack位,为 1,就表示,这个报文为 应答报文。
问题:应答报文,响应?
前面学到过 HTTP协议 ,就应该会知道,一个请求,会返回一个响应(一问一答)。
此处女神 根据我发送的信息,返回给我的信息,能不能叫做 响应 ?
答:不行。
请求和响应 ,我们是站在业务的角度来说的 。
此处涉及的 应答报文,和业务是无关的。
其实,我上面的信息,如果要以 应答报文 来严格要求的话,应该是这样的:

接收方,返回的应答报文 ,只是告诉发送方,接收方,接收到了你发送过来的数据了。
应答报文的信息,只会是 "收到"。
我之前的表示,只是为了,能让这个例子,更加生动一点,才添加了点业务表现("滚","好啊好啊" 等 响应信息)。
1.3 编写序号规则:
32位序号 编写序号规则
32位序号 & 32位确认序号,更详细的编写序号规则,如图所示:

TCP协议 ,其中一个特点是:面向字节流
所以,真实的编写序号情况 ,不是按照信息的 1 条、2 条的方式来编号的。
而是按照 "字节" 来编写序号的 。
每一个字节分配一个序号 ,这个序号,是连续递增的(1、2、3、4、5......)
TCP协议 编写序号 ,是对 载荷 部分(应用层数据包),编写序号 。
一个 TCP 的载荷,是由多个字节构成 的。
一个字节分配一个序号,多个字节分配多个序号。
此处的 32位序号(就表示一个序号) ,只能填写一个序号 ,应该要填写哪一个序号 ?
答:序号(TCP报文中的 32位序号),填写的编号,是载荷部分的第一个字节的序号。
因为每一个字节,分配一个序号,且这个序号,是连续递增的 。
知道了第一个字节,就知道下一个是第二个字节,依次往下,结合载荷部分的长度,就可以知道,每一个字节的序号,是多少了。
32位确认序号 的编写序号规则
32位确认序号 的编写序号规则 是:把收到的数据载荷的最后一个字节序号 + 1,填写到确认序号中 。
例如,收到的数据载荷的最后一个字节的序号是:1000
1000 + 1 = 1001,1001,就是确认序号。
我们来看这么一张图:

上面这张图的第一个确认序号是 1001,它所表示的含义是 :
(1):<(小于)1001 的数据,主机B 都已经确认收到了 (这个要重点理解,滑动窗口机制 中,会使用到这个含义)
(2):接下来,要从编号 1001 的字节开始给主机B发送
确认序号为什么要这样设计?
我们讲到超时重传,再解释。
1.4 接收缓冲区
接收方,收到了带有序号的数据 ,是直接进行排序的吗?
答:不是,都是先放到 接收缓冲区 中。
TCP 在接收方这里,会安排 "接收缓冲区" (一块内存,是操作系统内核里的) 。
通过网卡读取到的数据,先放到 数据缓冲区 中,后续代码中调用 read方法读取数据时 ,会根据序号 ,在 数据缓冲区 中,进行排序。
序号小的在前面,序号大的在后面 。
确保前面的数据,都已经到了 ,read 才能解除阻塞,读取到数据 。
如果是后面的数据先到 ,read 继续阻塞,不会读取到数据。
好比如上面我们说的,接亲车队的例子。
如果新娘家,离新郎家,距离较远,由于路况复杂,接亲车队是比较容易走散的。
我们给车队的车,编个序号:

走散之后:

此时,先到的车,会先在 新娘 家的路口(小区门口)等一下,等到头车也到了,根据序号排好车队,再去接亲 。

1.5 需要注意的两个点:
注意:编号,只针对载荷部分
32位序号 和 32位确认序号,进行编号 的时候,只针对载荷部分,TCP报头部分,是不参与编号的。
注意:序号,是用来排序的
我们上述说,对载荷数据进行编号,这个序号的作用 ,不是用来规定数据到达的先后顺序。
而是,会根据这个序号,对数据进行重新排序,保证数据的有序性。不出现 后发先至 的问题。
1.6 可能会疑惑的地方:
问题一:这个车队,相当于一个请求?
一个车队,是 N 个 TCP请求,每辆车是一个 TCP请求,这 N个TCP请求,可能共同表示的是一个应用层数据包,也有可能是 M 个应用层数据包,这就要看,应用层协议是怎么定义的了。
TCP 不关心应用层咋搞,都是按照字节来传输的。
问题二:TCP,数据分段传输,害怕黑客入侵吗?
黑客入侵的设备,可能是有 TCP的处理能力的。
虽然我们说,路由器工作在网络层,不工作在传输层,但是这是经典,传统的说法。
硬件设备高速发展的 2025 年,现代的路由器,一般都能处理传输层的数据。
路由器这边有 TCP 的情况下,也会有接收缓冲区,也会进行排队,也能确保路由器上的应用层数据包是完整有序的。
1.7 总结:
在没有引入序号之前 ,发送方发送数据,接收方接收数据,接收方返回数据后,如果遭遇网络拥堵 的情况下,接收方收到的信息顺序,就会出现:后发先至的情况。
引入序号之后 ,接收方就可以根据序号,对数据进行排序。
出现 后发先至 的情况,TCP会进行处理,确保代码里读到的数据 (InputStream类,调用 read 方法),和发送方写入的数据顺序一致(OutputStream类,调用 write 方法),确保应用程序通过 socket api 读取到的数据顺序是正确的。
基于 TCP 写代码的时候,完全不用担心数据顺序的问题。
代码写起来,比较方便。
基于 UDP 写代码的时候,由于 UDP 支持的最大长度很小,只有 64KB,要进行拆包组包,就要考虑顺序的问题,自己实现排序逻辑。
代码写起来,复杂,工作量还大。