📚相关专栏:计算机网络那些事
在上一篇文章中,我们研究了UDP报文的结构,由于其报头中只有两个字节来记录报文长度,导致一次最多只能传输 64KB 的数据,这完全无法满足当前对于海量数据传输的需求。于是,TCP协议便应运而生,TCP也是互联网中最广泛使用的传输层协议之一。那么,TCP是如何解决这些问题的?与UDP相比又有哪些区别?就让小编带着大家一起来好好研究一下吧。
一、TCP报文结构
我们先来看一下TCP报文的结构是什么样的吧:
直接这么看,可能会十分抽象,没关系,后面小编都会一一进行解释的。
源端口、目的端口以及校验和这三个信息与UDP的相差不大,想要了解的可以看一下 UDP报文详解 这篇文章,这里就不再做过多赘述了。本篇文章就主要先来介绍一下首部长度、序号与确认序号的具体作用吧。
首部长度:
对于UDP来说,报头的长度就是固定的8个字节,而对于TCP来说,报头的长度是可以根据需要变长的。
首部长度默认是 4个比特位,也就是说其可以表示0到15的范围,不过要注意的是,这里的单位不是 字节 ,而是 4个字节,也就是说假如首部长度记录的是 “1111”,也就是数值 15,其代表整个报头的长度是 15x4 ,也就是 60 个字节。这些都是协议约定好的,我们无权干预,只要遵守就好了。
值得注意的是,在默认4位的基础上,TCP协议还提供了 6 位的保留位,其实这也是为了未雨绸缪。当年UDP就是由于限制死了报文的长度,导致无法顺应时代发展。
TCP就充分吸收了这一教训,这 6位的保留位就是为了TCP协议后续的可拓展性而准备的。当未来某一天,TCP需要新增属性/或某个属性的长度不够用的时候,就可以直接利用保留位来加长报头的长度,TCP的结构也不需要发生太大变化,像这样后续升级扩展就会比较容易了
序号与确认序号
这两个数据主要是为了保障TCP协议的可靠性而存在的。
UDP协议在发出信息后就没有反馈了,发出方无法确定数据是否成功传输到接收方,也无法做出任何处理,因此UDP就被称之为是一种不可靠传输,无法保证所有数据都能传输成功。
相比之下,TCP就采用了一系列的机制,来保障数据传输的可靠性,比如:
- 让发送方确认接收方是否成功接收
- 如果接收方在一定时间内都没有成功接收,发送方就会重新发送一遍数据
这两个操作分别对应着TCP协议的两个核心机制:
- 确认应答
- 超时重传
接下来小编就来详细详细介绍一下这两个机制
二、确认应答
1、流程
所谓的“确认应答”机制就是,接收方收到数据后,会想发送方返回一个“应答报文”(ack/acknowledge),相当于告诉发送方“我收到了!”。TCP就引入了序号和确认序号,来使应答报文和传输数据能够一一对应:
每个字节都有一个独立的编号,字节与字节之间,编号是连续且递增的。
当我们向接收方发送一段数据时,会在序列号中记录当前数据报载荷中第一个字节的编号。比如我要发送序列号为 1~1000的数据时,就会在序列号中记录编号“1”:
作为应答方,就会在接收到数据后,记录此次接收数据最后一个字节的编号x,并会给发送方返回一个应答报文,在确认号中记录 x+1,比如在接收到 1~1000的数据后,就会在确认序号中记录“1001”
因为序列号是连续且递增的,因此这就相当于告诉发送方编号“1001”前的数据都成功接收到了,这样发送方在下一次发送数据时,就会从“1001”开始继续往下编号。以此往复,从而达到“确认应答”的效果。
注意:
由于序列号只有32位/4字节,表示范围只有0->42亿9千万。不过这不代表最多只能传这么多的数据,当编号达到上限时,就会重新从 0 开始编号。即 “0 -> 42亿9千万 ->0”的循环编号。
2、标志位
刚才我们一直没有谈到的这几个东西,就被称之为“标志位”,与TCP的一些核心机制密切相关,用来标记此次报文的属性,比如通过“ack”字段来区分普通报文与应答报文。
- 对于普通报文,ack为0,“序列号”是有效的,“确认序号”是无效的
- 对于应答报文,ack为1,“序列号”和“确认序号”都是有效的
应答报文在默认情况下一般不会携带数据
3、后发先至问题
正常情况下,接收方与应答方应该是下图这样的:
但是,在网络传输中,很可能会出现“后发先至”的问题,我后来发送的数据对方反而可能先收到,就会出现下面这种窘状:
这与实际意图就差之千里了。
为啥网络会存在“后发先至”呢?
因为在正常传输数据时,每个数据包走的线路可能都是不同的,线路不同,遇到的种种状况肯定也会有差别,最终到达的时序也就无法确定了,很可能就会出现后发出反而先收到的情况了
针对这一问题,TCP就可以针对接收方收到的数据,根据序号重新进行排序,确保应用程序读到的数据一定与发送方发出的数据顺序一致。
- 假如发送方A发送的数据为1~1000,1001~2000,2001~3000
- 此时接收方B就有可能会先接收到1001~2000和2001~3000,这时B就不会立即读取,而是会进入阻塞等待,一直等到接收到 1~1000的数据时,才会重新将数据递增排序,确保与发送方发出数据一致时,才会正式读取。
接收方这边在操作系统内核中,会有一段内存空间,作为“接收缓冲区”,收到的数据,会先在接收缓冲区中排队等待,只有当开头的数据到达,应用程序才能真正读取到其中的数据
三、超时重传
1、丢包问题
网络传输并不会一帆风顺,可能会出现各种各样的原因导致数据丢失/损坏,造成“丢包”。
以下列举几种可能引起丢包的原因:
(1)比特翻转
在数据传输过程中,受外界环境的影响,比如磁场变化等,很可能导致数据出现“bit翻转”问题,即数据中 0->1,1->0,收到这个数据的接收方/中间的路由器等,计算校验和时发现与发送方校验和对不上,就会直接将这个数据包丢弃,不再继续往后转发/不交给应用层使用,就会导致“丢包问题”
(2)负载过大
某个网络节点,单位时间只能转发 N 个包,在网络高峰期,该网络节点所需转发的包超过阈值了(负载过大),此时后续传输来的数据就可能直接被丢弃掉,导致“丢包问题”
注意:
丢包问题是完全随机,不可预测的,无论TCP协议如何优化也不可能完全避免丢包问题的产生。因此TCP能做的就是尽量感知到数据是否丢包,并做出一些处理
2、TCP对丢包问题的解决方案
TCP通过应答报文来确定是否丢包,如果一段时间内没有收到应答报文就认为发生丢包了。
丢包问题主要分为两种:
- 发送方发送的数据报文丢包
- 接收方返回的应答报文丢包
(1)数据报文丢包
发送方在发出数据后,会给出一个 “时间限制”(超时时间)
如果在这个时间限制内,没有收到ack应答报文,就认为数据丢包了
此时发送方就会重发一遍数据
(2)应答报文丢包
由于发送方无法区分是报文丢包还是ack应答报文丢包,因此在一段时间后都会进行“超时重发”操作,这样B就会收到两份一模一样的数据。
因此TCP协议需要能够识别出哪些包是重复的包, 并且把重复的丢弃掉。这时候我们可以利⽤前⾯提到的序列号, 就可以很容易做到去重的效果。
在接收方那有一个接收缓冲区,收到的数据会先进入缓冲区中,后续收到的数据,会根据编号,在缓冲区中进行排序,如果发现当前序号 1~1000,在缓冲区中已经存在了,就会直接把该数据丢弃掉,从而避免重复读取同一段数据。
3、超时重传的时间设定
超时重传的时间并不是固定值,而是会发生动态变化的
比如发送方第一次重传,超时时间是t1,在重传之后,就会再次设定新的超时时间t2。
其中 t2>t1 ,每多重传一次,超时时间的间隔就会变大,重传的频次会降低
之所以如此设定,是因为经过一次重传之后,数据成功传输到接收方的概率就会提升很多,每多重传一次,概率都会越来越高。
换句话说,如果在多次重传的情况下,数据都没有成功传输,说明此时网络的丢包率,已经高的离谱了,可以说此时网络可能发生了严重故障,大概率已经无法继续使用了。
此时无论你重传的多快,重传的次数再多,也无力回天,还不如缓重传速度,节约一些资源。
注意:
重传并不会无休止地进行,当重传达到一定次数后TCP就会认为这个连接已经失效了,不会再尝试重传了。此时TCP会尝试进行“重置/复位 连接”,发送一个特殊的数据包“复位报文”,如果此时网络恢复了,复位报文就会重置连接,使通信可以继续进行。如果此时网络还是有严重问题,复位报文也没有得到回应,此时TCP就会单方面断开连接,即删除之前保存的接收方的相关信息。
那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊