【网络原理】TCP保持“可靠传输”的秘密--确认应答与超时重传机制

ee010874ffb04778851a33867217cf98.png

 ????个人主页初晴~

????相关专栏:计算机网络那些事


        在上一篇文章中,我们研究了UDP报文的结构,由于其报头中只有两个字节来记录报文长度,导致一次最多只能传输 64KB 的数据,这完全无法满足当前对于海量数据传输的需求。于是,TCP协议便应运而生,TCP也是互联网中最广泛使用的传输层协议之一。那么,TCP是如何解决这些问题的?与UDP相比又有哪些区别?就让小编带着大家一起来好好研究一下吧。

一、TCP报文结构

我们先来看一下TCP报文的结构是什么样的吧:

8dad4eb8463c4ed389a8bd40012ba857.png

直接这么看,可能会十分抽象,没关系,后面小编都会一一进行解释的。

源端口、目的端口以及校验和这三个信息与UDP的相差不大,想要了解的可以看一下 UDP报文详解 这篇文章,这里就不再做过多赘述了。本篇文章就主要先来介绍一下首部长度序号确认序号的具体作用吧。

首部长度:

d5f25a5cdb1343afaccee06035dd9acf.png

对于UDP来说,报头的长度就是固定的8个字节,而对于TCP来说,报头的长度是可以根据需要变长的。

首部长度默认是 4个比特位,也就是说其可以表示0到15的范围,不过要注意的是,这里的单位不是 字节 ,而是 4个字节,也就是说假如首部长度记录的是 “1111”,也就是数值 15,其代表整个报头的长度是 15x4 ,也就是 60  个字节。这些都是协议约定好的,我们无权干预,只要遵守就好了。

值得注意的是,在默认4位的基础上,TCP协议还提供了 6 位的保留位,其实这也是为了未雨绸缪。当年UDP就是由于限制死了报文的长度,导致无法顺应时代发展。

TCP就充分吸收了这一教训,这 6位的保留位就是为了TCP协议后续的可拓展性而准备的。当未来某一天,TCP需要新增属性/或某个属性的长度不够用的时候,就可以直接利用保留位来加长报头的长度,TCP的结构也不需要发生太大变化,像这样后续升级扩展就会比较容易了

序号与确认序号

39648b7e1c324d3b92aeda91cc9d9167.png

这两个数据主要是为了保障TCP协议的可靠性而存在的。

UDP协议在发出信息后就没有反馈了,发出方无法确定数据是否成功传输到接收方,也无法做出任何处理,因此UDP就被称之为是一种不可靠传输,无法保证所有数据都能传输成功。

相比之下,TCP就采用了一系列的机制,来保障数据传输的可靠性,比如:

  • 让发送方确认接收方是否成功接收
  • 如果接收方在一定时间内都没有成功接收,发送方就会重新发送一遍数据

这两个操作分别对应着TCP协议的两个核心机制:

  • 确认应答
  • 超时重传

接下来小编就来详细详细介绍一下这两个机制


二、确认应答

1、流程

所谓的“确认应答”机制就是,接收方收到数据后,会想发送方返回一个“应答报文”(ack/acknowledge),相当于告诉发送方“我收到了!”。TCP就引入了序号确认序号,来使应答报文和传输数据能够一一对应:

52cd6f464cdf44d7b12ecd14c797e08d.png

由于TCP是 面向字节流的,这里的编号并非是按照“第一条,第二条”这样的方式来进行编排的,而是按照 “字节”来编排, 将每个字节的数据都进⾏了编号。即为 序列号

144785d69e484f3b948f39c3446af53c.png

每个字节都有一个独立的编号,字节与字节之间,编号是连续且递增的。

当我们向接收方发送一段数据时,会在序列号中记录当前数据报载荷中第一个字节编号。比如我要发送序列号为 1~1000的数据时,就会在序列号中记录编号“1”

be24067b6f944ba3a0919c0afc48887e.png

作为应答方,就会在接收到数据后,记录此次接收数据最后一个字节编号x,并会给发送方返回一个应答报文,在确认号中记录 x+1,比如在接收到 1~1000的数据后,就会在确认序号中记录“1001”

e0e4134b090d41da8d9440cc1f3cd3b9.png

因为序列号是连续且递增的,因此这就相当于告诉发送方编号“1001”前的数据都成功接收到了,这样发送方在下一次发送数据时,就会从“1001”开始继续往下编号。以此往复,从而达到“确认应答”的效果。

注意:

由于序列号只有32位/4字节,表示范围只有0->42亿9千万。不过这不代表最多只能传这么多的数据,当编号达到上限时,就会重新从 0 开始编号。即 “0 -> 42亿9千万 ->0”循环编号

2、标志位

刚才我们一直没有谈到的这几个东西,就被称之为“标志位”,与TCP的一些核心机制密切相关,用来标记此次报文的属性,比如通过“ack”字段来区分普通报文应答报文

3ed9dd046d584b07b369bb3c68474e53.png

  • 对于普通报文,ack为0,“序列号”是有效的,“确认序号”是无效的
  • 对于应答报文,ack为1,“序列号”“确认序号”都是有效的

应答报文在默认情况下一般不会携带数据

3、后发先至问题

正常情况下,接收方与应答方应该是下图这样的:

1c78c71ee0104f06a91086cafd2d4f22.png

但是,在网络传输中,很可能会出现“后发先至”的问题,我后来发送的数据对方反而可能先收到,就会出现下面这种窘状:

ca248ffd1b194ad8b4fa59dd90e14263.png

这与实际意图就差之千里了。

为啥网络会存在“后发先至”呢?

因为在正常传输数据时,每个数据包走的线路可能都是不同的,线路不同,遇到的种种状况肯定也会有差别,最终到达的时序也就无法确定了,很可能就会出现后发出反而先收到的情况了

针对这一问题,TCP就可以针对接收方收到的数据,根据序号重新进行排序,确保应用程序读到的数据一定与发送方发出的数据顺序一致

  • 假如发送方A发送的数据为1~1000,1001~2000,2001~3000
  • 此时接收方B就有可能会先接收到1001~20002001~3000,这时B就不会立即读取,而是会进入阻塞等待,一直等到接收到 1~1000的数据时,才会重新将数据递增排序,确保与发送方发出数据一致时,才会正式读取。

7bcd383ab9d24ef8a1a8e86c8283f7ee.png

接收方这边在操作系统内核中,会有一段内存空间,作为“接收缓冲区”,收到的数据,会先在接收缓冲区中排队等待,只有当开头的数据到达,应用程序才能真正读取到其中的数据 

三、超时重传

1、丢包问题

网络传输并不会一帆风顺,可能会出现各种各样的原因导致数据丢失/损坏,造成“丢包”。

以下列举几种可能引起丢包的原因:

(1)比特翻转

在数据传输过程中,受外界环境的影响,比如磁场变化等,很可能导致数据出现“bit翻转”问题,即数据中 0->1,1->0,收到这个数据的接收方/中间的路由器等,计算校验和时发现与发送方校验和对不上,就会直接将这个数据包丢弃,不再继续往后转发/不交给应用层使用,就会导致“丢包问题”

3dc0eb5d1d8742799adaf301fd5829d4.png

(2)负载过大

某个网络节点,单位时间只能转发 N 个包,在网络高峰期,该网络节点所需转发的包超过阈值了(负载过大),此时后续传输来的数据就可能直接被丢弃掉,导致“丢包问题”

650f55e9dbde4adf9e358a91787a87fe.png

注意:

丢包问题是完全随机,不可预测的,无论TCP协议如何优化也不可能完全避免丢包问题的产生。因此TCP能做的就是尽量感知到数据是否丢包,并做出一些处理

2、TCP对丢包问题的解决方案

TCP通过应答报文来确定是否丢包,如果一段时间内没有收到应答报文就认为发生丢包了。

丢包问题主要分为两种:

  • 发送方发送的数据报文丢包
  • 接收方返回的应答报文丢包

(1)数据报文丢包

14796c5cd9d54cf9a5a0e9576fe2ac77.png

 发送方在发出数据后,会给出一个 “时间限制”(超时时间)

如果在这个时间限制内,没有收到ack应答报文,就认为数据丢包了

此时发送方就会重发一遍数据

(2)应答报文丢包

c783e464ded9483c958e631f0ed007af.png

由于发送方无法区分是报文丢包还是ack应答报文丢包,因此在一段时间后都会进行“超时重发”操作,这样B就会收到两份一模一样的数据。

因此TCP协议需要能够识别出哪些包是重复的包, 并且把重复的丢弃掉。这时候我们可以利⽤前⾯提到的序列号, 就可以很容易做到去重的效果。

在接收方那有一个接收缓冲区,收到的数据会先进入缓冲区中,后续收到的数据,会根据编号,在缓冲区中进行排序,如果发现当前序号 1~1000,在缓冲区中已经存在了,就会直接把该数据丢弃掉,从而避免重复读取同一段数据。

3、超时重传的时间设定

超时重传的时间并不是固定值,而是会发生动态变化

比如发送方第一次重传,超时时间是t1,在重传之后,就会再次设定新的超时时间t2。

其中 t2>t1 ,每多重传一次,超时时间的间隔就会变大重传的频次会降低

之所以如此设定,是因为经过一次重传之后,数据成功传输到接收方的概率就会提升很多,每多重传一次,概率都会越来越高。

换句话说,如果在多次重传的情况下,数据都没有成功传输,说明此时网络的丢包率,已经高的离谱了,可以说此时网络可能发生了严重故障,大概率已经无法继续使用了。

此时无论你重传的多快,重传的次数再多,也无力回天,还不如缓重传速度,节约一些资源。

注意:

重传并不会无休止地进行,当重传达到一定次数后TCP就会认为这个连接已经失效了,不会再尝试重传了。此时TCP会尝试进行“重置/复位 连接”,发送一个特殊的数据包“复位报文”,如果此时网络恢复了,复位报文就会重置连接,使通信可以继续进行。如果此时网络还是有严重问题,复位报文也没有得到回应,此时TCP就会单方面断开连接,即删除之前保存的接收方的相关信息


那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

8a8bbe7c4c814fa79a3f86d9af5186e1.png

上一篇:系统架构设计师教程 第13章 13.4 数据访问层设计 笔记


下一篇:Python Kivy 基础教程