上一篇文章中,我们已经初步认识了TCP协议,从Linux内核对网络报文的管理,到TCP报头的基本结构,再到面向字节流的核心特性,相信大家对TCP已经有了一定的了解。不过,这些还只是TCP的基础内容。真正让TCP区别于UDP的关键之一,就是它能够提供可靠的数据传输服务。
那么问题来了,TCP究竟是如何保证数据可靠传输的?通信双方又是怎样完成数据交互的?
从本文开始,我们就围绕TCP的可靠性展开,先了解TCP通信的一般过程,再重点分析报头中的32位序号和32位确认序号,看看它们在数据传输过程中究竟发挥着什么作用。
特别提醒:从本文开始,后面的内容将进入校招面试的高频考点密集区。对于一些曾经被公司考察过的知识点,我会在文章中重点标注,并结合原理进行讲解。大家不仅要知道答案是什么,更要弄清楚为什么是这个答案。
好了,话不多说,我们正式开始!
目录
[2.1 TCP的基本通信过程:从简单的数据收发说起](#2.1 TCP的基本通信过程:从简单的数据收发说起)
[2.2 TCP的双向通信机制:从确认应答到捎带应答](#2.2 TCP的双向通信机制:从确认应答到捎带应答)
[2.3 为什么TCP同时需要32位序号与32位确认序号?](#2.3 为什么TCP同时需要32位序号与32位确认序号?)
[2.3.1 两个字段的根本区别:数据编号与接收确认](#2.3.1 两个字段的根本区别:数据编号与接收确认)
[2.3.2 确认序号的真正含义:累计确认与丢包处理](#2.3.2 确认序号的真正含义:累计确认与丢包处理)
[2.3.3 TCP如何解决乱序问题:从序号识别到数据有序交付](#2.3.3 TCP如何解决乱序问题:从序号识别到数据有序交付)
一、TCP可靠性的本质:为什么TCP能够实现可靠传输
在正式讲解TCP的可靠性之前,我们先来思考一个问题:**世界上真的存在100%绝对可靠的通信系统吗?**答案是:不存在!毕竟,网络环境是复杂的,数据在传输过程中可能发生丢失、延迟,甚至连接中断。我们无法保证任何情况下数据都一定能够成功送达。但这并不意味着可靠通信就无法实现。
**虽然我们无法从根本上消除网络中的不确定性,但可以通过一系列协议机制,尽可能保证数据可靠、有序地到达对方。**而TCP正是这样设计的。
那么,TCP可靠性的核心思想是什么呢?我们先从一个比较朴素的角度来理解:
-
确认应答(ACK): 发送方将数据发送出去后,如果收到了接收方对应的确认应答,就能够知道相应的数据已经被对方的TCP协议栈成功接收。也就是说,确认应答让发送方有办法判断之前发送的数据是否已经到达。
-
未确认的数据: 对于刚刚发送出去、还没有收到确认应答的数据,发送方暂时无法确定它是否已经成功到达。因为数据可能还在网络中传输,也可能已经到达,只是确认应答还没回来,甚至可能在途中丢失。
看到这里,可能有人会觉得有点抽象。
为什么收到应答,就能够确认之前发送的数据已经到达?而刚刚发送出去的数据,又为什么不能直接认定它已经成功送达呢?
其实,这个道理并不难理解。
这里我画了一张板书,大家可以结合图中的对话过程来看一下。

假设客户端和服务器相距几千米,双方正在通过网络进行通信。
客户端先问服务器一句:"吃了吗?"
服务器收到后,回复:"吃了。"
当客户端收到这句回复时,就可以推断,服务器已经收到了之前那句"吃了吗?",否则它又怎么知道我们在问什么呢?
紧接着,客户端又问:"吃了什么?"
服务器回复:"吃了饭。"
同样,当客户端收到这个回复时,也就知道前面发送的消息已经成功到达了。
但是,如果客户端又发送了一句话,而服务器迟迟没有回复呢?
这时候,客户端还能确定这句话已经成功到达了吗?
显然不能!
因为消息可能已经送达,只是服务器还没回复;也可能消息在传输过程中就已经丢失了。
这就引出了一个非常重要的结论:发送出去,不等于对方已经收到;只有收到相应的确认,发送方才能知道对应的数据已经成功到达。
不过,上面只是为了方便理解而举出的生活例子。真正的TCP确认应答并不是通过分析对话内容实现的,而是依靠TCP报头中的确认序号(ACK)等机制,确认哪些字节已经被接收方的TCP协议栈收到。
那么,新的问题又来了。如果数据已经成功到达,但是接收方返回的 ACK 在网络中丢失了,该怎么办?
难道发送方就只能一直等下去吗?当然不是!这时候,TCP还可以通过超时重传等机制,对尚未确认的数据进行重新发送,并配合序号机制处理可能出现的重复数据。
不过,这些机制也不能保证网络发生永久故障时,数据仍然一定能够送达。TCP能做的是在连接正常工作的条件下,尽可能实现可靠传输,并在无法继续通信时报告失败。
所以,我们可以先记住一个核心思想:
TCP可靠性的重要基础,就是确认应答机制。发送方通过ACK了解哪些数据已经被接收,而对于尚未确认的数据,则需要借助重传等机制进一步保障可靠性。
当然,TCP的可靠性并不只有确认应答这么简单,后面我们还会逐步讲解序列号、超时重传、流量控制等相关机制。不过,在正式展开这些内容之前,我们还得先弄清楚一个更加基础的问题:
TCP的客户端和服务器,平时究竟是怎样进行数据交互的?
接下来,我们就先从TCP通信的一般过程入手,建立一个整体的认识,再逐步深入它背后的可靠传输机制。
二、TCP报文传输机制:从基本通信到序号与确认应答
2.1 TCP的基本通信过程:从简单的数据收发说起
前面我们已经了解了TCP可靠性的基本思想,知道了确认应答(ACK)机制在可靠传输中发挥着非常重要的作用。
不过,光知道有确认应答还不够。客户端和服务器在实际通信时,究竟是如何通过ACK来确认数据已经成功到达的呢?
这里我们先不考虑那些复杂的机制,就从最简单的通信过程入手。
我画了一张板书,大家可以结合下面的过程来看一下。

假设客户端需要向服务器发送一段数据。
客户端先将数据发送出去,服务器收到后,再向客户端返回一个确认应答(ACK),告诉客户端:"你刚才发送的数据,我已经收到了!"
当客户端收到这个ACK后,就可以确认对应的数据已经成功到达服务器的TCP协议栈。
这样一来,我们就有了从客户端到服务器这个方向上的可靠传输基础。
那么,反过来呢?如果服务器也需要向客户端发送数据,道理其实是一样的。
服务器负责发送数据,客户端收到后,再返回相应的ACK。服务器收到确认后,也就知道自己发送的数据已经成功到达对方。
所以,无论是客户端向服务器发送数据,还是服务器向客户端发送数据,都可以通过确认应答机制,了解对应的数据是否已经被接收。
而这种发送数据、接收确认的机制,就是我们前面提到的 确认应答机制(ACK)。
不过,这里还有一个非常关键的原则:
不对纯ACK应答再进行ACK!!!
为什么呢?大家想一想,如果客户端收到服务器的ACK后,还要再给服务器发送一个ACK,告诉它"你的ACK我收到了"。
那么,服务器收到这个ACK后,是不是又得回复一个ACK,表示"你的ACK我也收到了"?
好家伙,这不就没完没了吗?
如果每个确认应答都必须再得到一次确认,那么通信双方就可能陷入无休止的应答循环。
所以,TCP不会仅仅因为收到一个不携带数据的纯ACK,就再专门发送一个ACK。
当然,如果某个TCP报文在携带ACK的同时还携带了实际数据,那么其中的数据仍然需要按照TCP的可靠传输机制进行确认。这两种情况可不能混为一谈。
好了,到这里,我们已经能够初步理解TCP是如何利用确认应答机制保障数据可靠传输的了。
**但是,新的问题又来了。**假设我们采用最简单的"一发一答"模式:发送方每发送一段数据,都必须等到对应的ACK返回之后,才能发送下一段数据。
这种方式虽然容易理解,但效率是不是有点太低了?
尤其是通信双方距离比较远、网络延迟比较大的时候,发送方明明还有大量数据需要发送,却只能等着ACK回来。
**这显然会浪费不少传输时间和网络带宽。**那么,有没有办法让发送方不必每发送一段数据就停下来等待,而是能够连续发送多段数据,再通过确认应答机制统一管理呢?
当然有! 实际上,TCP并不是简单地采用这种"一发一答、发完再等"的方式,而是通过更加高效的机制,允许一定数量的数据在尚未收到确认时继续传输。至于这个过程究竟是怎么实现的,我们接着往下看------TCP 传递报文更加通用的过程。
2.2 TCP的双向通信机制:从确认应答到捎带应答
前面我们已经了解了TCP最基本的确认应答机制。不过,如果每发送一个报文,都必须等到对应的ACK返回之后,才能继续发送下一个报文,那效率未免也太低了。尤其是在网络延迟比较大的情况下,发送方明明还有大量数据需要发送,却只能干等着确认应答回来。
那么,有没有办法在保证可靠性的同时,进一步提高数据传输的效率呢?
当然有!实际上,TCP并不是简单地采用"一发一答"的通信方式,而是允许发送方在尚未收到前面数据的确认时,继续发送一定数量的数据。
我们暂时可以把这个过程理解为:发送方连续发送多个TCP报文段,接收方再通过确认应答,告诉发送方哪些数据已经成功收到。
具体怎么理解呢?这里我画了一张板书,大家可以结合图中的过程来看一下。

假设客户端需要向服务器发送四个TCP报文段。
为了提高传输效率,客户端不再发送一个就停下来等待,而是连续将这四个报文段发送出去。
如果网络通信一切正常,服务器就能够收到这些数据,并根据 TCP 的确认应答机制,向客户端返回相应的ACK。
不过,网络环境可不是每次都这么顺利的!
假设这四个报文段在传输过程中,其中一个不幸丢失了。那么问题来了:
客户端怎么知道究竟是哪一段数据丢失了呢?
你可能会想,既然发送了四个报文段,却只收到了三个确认应答,那不就说明有一个报文段丢失了吗?听起来好像有点道理,但仔细想想,真的能够这样判断吗?
**其实不能!**因为TCP的确认应答并不是简单地按照"发送一个报文段,就必须对应返回一个 ACK"的方式工作的。
TCP采用的是累计确认机制。接收方可以通过一个确认序号,告诉发送方:在某个序号之前的数据,我已经连续收到了。而且,接收方还可能采用延迟确认等策略,并不是每收到一个报文段,都要立即返回一个独立的ACK。
所以,我们不能单纯通过发送了多少个报文段、收到了多少个 ACK,就判断究竟是哪一段数据发生了丢失。
那么,TCP到底是怎么做到的呢?既然多个报文段可以连续发送,那发送方就必须有办法区分这些数据,知道哪些已经被确认,哪些还没有被确认。
否则,一旦发生丢包、乱序,甚至需要重新发送数据,岂不是全乱套了?
这时候,TCP报头中的两个重要字段就派上用场了!
我们再回过头来看看TCP的报文格式。
在TCP报头中,有两个非常关键的字段:
-
32位序号(Sequence Number): 用于标识当前TCP报文段中数据的起始字节序号,让通信双方能够识别字节在整个数据流中的位置。
-
32位确认序号(Acknowledgment Number): 用于告诉发送方,接收方下一步期望收到哪个字节。换句话说,它可以确认该序号之前连续的数据已经成功接收。

注意,TCP的序号并不是简单地给每个报文段编号,而是对字节流中的字节进行编号。
有了这两个字段,TCP就能够在连续发送多个报文段的情况下,跟踪数据的接收情况,并为后续的丢包检测、重传以及乱序重组提供基础。
说到这里,我们也就能理解,为什么TCP报头中需要专门设计两个32位的字段了。
它们并不是随便放在报头里的,而是TCP实现可靠、有序传输的重要基础。
那么,32位序号究竟是怎么编号的?确认序号为什么表示的是下一个期望收到的字节?如果中间某段数据丢失了,接收方又该如何应答?
这些问题,我们接下来一个个展开。
面试重点:32位序号与32位确认序号,是理解TCP确认应答、累计确认、超时重传以及滑动窗口机制的基础。这部分不仅要记住定义,更要能够结合具体的数据传输过程,解释序号和确认序号是如何变化的。
2.3 为什么TCP同时需要32位序号与32位确认序号?
前面我们已经知道,TCP允许发送方连续发送多个报文段,而不必每发送一个就停下来等待确认。
不过,这样一来,新的问题也就出现了。
如果多个报文段同时在网络中传输,其中某个报文段不幸丢失了,发送方该怎么知道究竟丢失了哪些数据?如果数据发生乱序,接收方又该如何恢复原来的顺序呢?
显然,光靠简单的"发送---应答"还不够。
为了解决这些问题,TCP在报头中设计了两个非常重要的字段:32位序号(Sequence Number)和32位确认序号(Acknowledgment Number)。
至于为什么需要两个字段,而不是只用一个就够了,我们接下来慢慢分析。
这里我先贴一张板书。 内容稍微有点多,看起来可能也有些乱。不过没关系,大家暂时不用急着把它全部看懂,后面我们会结合具体的通信过程,一点点拆开来讲。

在正式分析之前,我还得强调一个非常重要的细节:
TCP 通信双方传递的都是TCP报文段。即使没有携带应用层数据,也至少需要一个TCP报头!
什么意思呢?比如,客户端向服务器发送数据,服务器收到后需要返回ACK确认应答。那么,这个ACK是凭空发出去的吗?
当然不是!它同样需要通过TCP报文段来传递。
即使这个报文段没有携带任何应用层数据,也仍然需要包含TCP报头。标准的TCP固定报头至少占用20字节,其中就包括我们即将讨论的序号和确认序号。
所以,大家一定要记住:
TCP报文段可以没有应用层数据,但不能没有TCP报头。确认应答也要通过TCP报文来传递。
这个细节看起来不起眼,但对于后面理解为什么TCP要同时设计32位序号和32位确认序号,非常重要。先把这个概念记住,后面我们再回过头来看,你就会发现这两个字段的设计其实很有讲究。
2.3.1 两个字段的根本区别:数据编号与接收确认
前面我们已经知道,TCP报头中存在两个非常重要的字段:32位序号和32位确认序号。
不过,问题来了:既然都是序号,为什么非得设计两个字段呢?直接用一个字段,难道不行吗?
其实还真不行!因为TCP是一个全双工协议,客户端和服务器都可以同时发送和接收数据。如果只设计一个序号字段,那么当双方同时进行数据交互时,这个字段就很难同时表达两件事情:
一件是标记我自己发送的数据,另一件则是确认我已经收到的对方数据。
我们来举个简单的例子。假设客户端向服务器发送了一段数据,TCP报头中的序号为100,并且这段数据只包含1个字节。服务器收到后,就可以返回一个确认应答:
客户端发送:Seq = 100,数据长度 = 1 字节
服务器确认:Ack = 101
为什么确认序号是101呢?因为TCP的确认序号表示接收方下一个期望收到的字节序号。
既然序号为100的这个字节已经收到了,那么服务器接下来期望收到的自然就是101。
这里需要特别注意,确认序号并不是永远等于序号加1,而是要根据实际接收到的数据长度来计算。对于普通的数据报文段,我们可以先这样理解:
确认序号 = 连续收到的数据中最后一个字节的序号+ 1。
比如,客户端从序号100开始,一次发送了100个字节,那么服务器连续收到这些数据后,返回的确认序号就应该是200,而不是101。好了,继续往下看。
难道服务器收到客户端的数据后,就只能单纯返回一个ACK吗?
当然不是!别忘了,TCP是支持全双工通信的。服务器除了需要确认客户端发送的数据,也可能有自己的业务数据需要发送。那么,我们能不能把确认应答和服务器准备发送的数据,放在同一个TCP报文段中呢?
**当然可以!这就叫做捎带应答(Piggybacking)。**比如,服务器一边告诉客户端:"你刚才发送的数据我已经收到了",一边把自己准备发送的数据也带过去。这样一来,就不需要为了确认数据,额外发送一个独立的纯ACK报文段了,能够在一定程度上减少协议开销,提高传输效率。
不过,这里就出现了一个关键问题:这个报文段既要确认客户端的数据,又要标记服务器自己发送的数据,该怎么办呢?
很简单,分别使用两个字段!
-
32位序号(Seq): 标记当前报文段中自己发送的数据在本方向字节流中的位置。
-
32位确认序号(Ack): 告诉对方,我已经连续收到了哪些数据,下一步期望收到哪个字节。
而且,TCP通信双方各自维护自己的发送序号空间,彼此独立。
所以,这就是TCP为什么需要同时设计序号和确认序号的重要原因:既要给自己发送的数据编号,又要对对方发送的数据进行确认。
两者各司其职,TCP才能在双向通信过程中,同时完成数据传输与确认应答。
2.3.2 确认序号的真正含义:累计确认与丢包处理
理解了为什么需要两个序号,我们再来看其中的32位确认序号。
前面我们讲过,TCP为了提高传输效率,可以连续发送多个报文段,而不需要每发送一个就等待一次确认。
那么,如果接收方返回的某些ACK在网络中丢失了,会不会影响数据的可靠传输呢?
其实,TCP在这方面有一个非常巧妙的设计------累计确认。
什么意思呢?我们来举个例子。假设客户端连续发送四个TCP报文段,每个报文段都携带100个字节的数据:
| 报文段 | 起始序号Seq | 数据字节范围 |
|---|---|---|
| 第一个 | 1 | 1~100 |
| 第二个 | 101 | 101~200 |
| 第三个 | 201 | 201~300 |
| 第四个 | 301 | 301~400 |
如果服务器已经连续收到了这四段数据,那么它下一步期望收到的字节序号是多少呢?
**当然是401!**因此,服务器就可以返回:
Ack = 401
这个确认序号的含义就是:
序号401之前的所有字节,也就是1~400号字节,接收方已经连续收到了。
注意,这里确认的是连续收到的字节,而不是简单地确认某一个报文段。
那么,问题来了。假设服务器之前发送的某几个ACK,在网络中不幸丢失了,但客户端最终成功收到了Ack = 401。这时候,客户端还需要纠结之前的ACK为什么没收到吗?
**当然没必要!**因为最新收到的确认序号已经明确告诉客户端:401之前的所有字节,接收方都已经连续收到了。既然如此,前面那些确认应答即使丢失,也不一定需要单独重传。
这就是TCP累计确认机制的好处:后续更大的确认序号,可以覆盖前面已经连续接收的数据范围。
不过,这里还有一个细节。
如果中间某段数据真的丢失了,那么接收方就不能直接跳过它,确认后面尚未连续接收的数据。
比如,客户端发送了1~400号字节,但服务器只收到了1~200和301~400,中间的201~300却丢失了。那么,即使后面的301~400已经到达,服务器的累计确认序号仍然不能直接变成401,而应该停留在201,表示自己还在等待从201开始的数据。
这样一来,发送方就能够根据确认情况,进一步判断哪些数据可能尚未成功到达。
当然,具体如何判断丢包、什么时候需要重传,还涉及重复ACK、超时重传以及选择性确认等机制,我们后面再详细展开。
总的来说,确认序号并不是简单地对某一个历史报文进行确认,而是用于告诉发送方:到目前为止,我已经连续收到了哪个位置之前的数据。
至于"下一次发送就从确认序号开始"这句话,也不能直接理解成固定规则,因为发送方可能早就发送了后续尚未确认的数据。这个问题,我们暂时先放一放,等到后面讲解滑动窗口时,再回过头来分析。
2.3.3 TCP如何解决乱序问题:从序号识别到数据有序交付
除了帮助实现确认应答和可靠传输,32位序号还有一个非常重要的作用,那就是帮助TCP处理数据乱序问题。
什么叫乱序呢?其实很好理解。你先发送出去的数据,不一定就会先到达对方。
举个生活中的例子。假设有两个人从浙江出发,准备前往西藏。虽然第一个人比第二个人更早出发,但由于他们选择的路线不同,途中遇到的交通状况也不一样,第二个人完全有可能先到达目的地。
网络数据传输也是类似的道理。不同的 IP 数据包可能经过不同的网络路径,而且网络设备处理数据时,也可能产生不同程度的延迟。
因此,**发送顺序和实际到达顺序,并不一定完全一致。**那么问题来了,如果 TCP 数据发生了乱序,接收方该怎么办呢?总不能谁先到,就先把谁的数据交给应用层吧?
当然不行!因为TCP提供的是可靠、有序的字节流传输服务。如果直接把乱序数据交给应用程序,就可能破坏原本的数据顺序。
这时候,序号的作用就体现出来了。由于TCP为字节流中的数据分配了序号,接收方就能够根据序号判断数据原本应该处于什么位置。
比如,客户端按顺序发送了三个报文段:
第一个:Seq = 100,长度 = 100 字节
第二个:Seq = 200,长度 = 100 字节
第三个:Seq = 300,长度 = 100 字节
正常情况下,接收方应该按照100、200、300的顺序收到这些数据。
但如果网络中发生了乱序,实际到达顺序变成:
100 → 300 → 200
那该怎么办?
很简单,接收方可以根据序号判断数据的位置,并对乱序到达的数据进行缓存和重组。
当序号为100的数据到达后,接收方知道自己下一步期望收到的是200。
可偏偏先到达的是300。
这时候,接收方就能发现:中间从200开始的那段数据还没有收到!
对于能够缓存的乱序数据,TCP可以先将其保留下来,等待缺失的数据到达。
等序号为200的数据补齐之后,接收方就可以把这些连续的数据按照正确顺序组织起来,再交付给应用程序。
当然,实际内核并不是简单地给报文段做一次排序,还需要处理数据重叠、重复接收、接收缓冲区管理等问题。
不过,背后的核心思想是一样的:
通过序号识别数据在字节流中的位置,再将连续、有序的数据交付给应用层。
所以,我们也就能够得出一个结论:
TCP的序号解决了数据位置识别与有序重组的问题,而确认序号则让发送方知道接收方已经连续收到了哪些数据。
两者配合起来,为TCP处理乱序、确认接收以及后续的丢包重传提供了重要基础。
说到这里,大家应该已经能够初步理解,为什么TCP报头中需要同时设计32位序号和32位确认序号了。不过,这还只是它们最基础的作用。真正精彩的地方,还在后面TCP如何利用这些字段,实现更加复杂的可靠传输机制。
好了,到这里,本篇关于TCP可靠性与确认应答机制的内容就先告一段落了。
这一篇我们主要围绕 TCP 的可靠性展开,从最开始朴素的"一发一答",到多个报文段连续发送,再到为什么TCP报头中需要同时设计32位序号和32位确认序号,一步步分析了TCP实现可靠传输背后的基本思路。
在这个过程中,我们也弄清楚了几个非常重要的问题:为什么发送出去的数据不代表对方一定收到?为什么TCP不需要对纯ACK再进行确认?如果某个报文段丢失了,或者数据发生乱序,TCP又该如何利用序号和确认序号来识别和处理?
其实,回过头来看,TCP的可靠性并不是凭空实现的,而是由一套套相互配合的机制共同支撑起来的。 序号让数据有了明确的位置,确认序号让发送方知道哪些数据已经被连续接收,而累计确认机制又让TCP能够在一定程度上容忍ACK的丢失。
不过,光有这些还不够。如果发送的数据迟迟没有得到确认,TCP该怎么办?如果网络中出现大量丢包,又该如何处理?
这些问题,就需要我们继续深入TCP的其他可靠传输机制了。
另外,还是想强调一下,这一部分是校招面试中非常容易被追问的内容。 面试官可能不会直接问你"什么是序号",而是给出几个具体的报文段,让你分析确认序号应该是多少、数据丢失后会发生什么。所以,学习TCP不能只停留在背诵概念上,更重要的是能够结合实际的通信过程,把每一步为什么这样设计想明白。
理解原理,比单纯记住答案更加重要。 当这些机制真正串联起来时,很多看似复杂的TCP面试题,其实也就有了清晰的思路。
好了,本篇文章就先聊到这里。如果文章中有什么理解不到位或者讲错的地方,也欢迎大家在评论区指出。毕竟网络协议涉及的细节非常多,难免会有疏漏,大家一起交流、一起进步!
感谢大家的阅读,我们下篇文章再见!