前言
在这篇文章中,荔枝会整理可靠数据传输的几个协议和要求,从可靠数据传输来引出TCP的协议数据传输的可靠性的理解,同时荔枝也会讲述TCP的报文结构、断开和连接的过程、拥塞控制的理解。今天是2023年的第一天,祝大家新年快乐哈哈哈,希望荔枝能在2023年有所得、有所成、有所悟。。。
文章目录
一、可靠数据传输的实现
在开始梳理TCP的相关知识之前,我们先来看看可靠数据传输,荔枝在之前的文章里也提到了数据传输协议的可靠性。所谓的可靠是我们的发送端发送的数据报文能够完整并且次序不变的传输到接收端,但在实际的网络运输中,我们有时会出现比特缺失、比特错乱和丢包等问题。可靠数据传输的几个协议主要有:rdt1.0、rdt2.0、rdt2.1、rdt2.2、rdt3.0。
1.1 认识几个可靠数据传输协议
rdt1.0
我们在之前的文章中有提及网络层和运输层的关系,也讲清楚了数据报文是从运输层将应用程序的数据封装成报文段并在网络层进行报文传输的。在rdt1.0中,实现的前提假设是我们的底层信道完全可靠,因此不必担心出现差错,自然就不用反馈信息了。
rdt2.0
在rdt2.0协议中,我们需要考虑比特差错的情况。在这种协议之中,我们引入了一种反馈机制,即通过接收者回复报文(肯定确认ACK、否定确认NAK)来告诉发送方是否需要重新发送报文。我们也称这种基于重传机制的可靠数据传输协议为自动重传请求协议(ARQ)。这种机制包括三个过程:差错检测、接收方反馈、是否重传。基于这种协议下,数据传输的过程应该为:发送方产生一个带有检验和的数据分组并发送给接收方,之后就会进入等待的状态,直到接收到接收方的ACK或者是NAK分组。如果收到一个NAK分组,就会进行重传的操作。而且,特别注意的是:发送方在等待接收方发送的ACK或者是NAK分组的时候,不能从上层获取数据,也就不会发送新的数据了。因此,类似rdt2.0这样的协议又被成为停等协议。
rdt2.1 和 rdt2.2
在rdt2.0的时候,我们并没有考虑到ACK和NAK受损的问题。我们有如下的解决方法:一种是加入足够的检验和字段;第二种就是当发送方收到模糊的ACK或NAK分组的时候就直接开始重传即可,但为了保证接收方能够识别当前分组是不是发生ACK或NAK丢失或模糊的问题,我们需要引入序号来帮助我们区分。而rdt2.1和rdt2.2就是rdt2.0得修订,rdt2.2是在比特差错的信道上得一个无NAK的可靠数据传输协议,也就是发送方只需要检查再ACK报文中被确认的分组序号。
rdt3.0——比特交替协议
在上面的协议中,我们一直假设不存在丢包,但这是不可能的,我们需要考虑这个客观存在的事实。在前面停等协议的梳理时,我们知道发送方会一直在等待接收方给出的反馈,因此如果出现传输过程中的丢包问题,那么这个往返时延往往是不可预估的,同时若我们设定响应时间的阈值,可能存在一个分组的时延超过了阈值而激发了重传机制导致冗余数据分组的出现。当然了,我们可以通过rdt2.2中讲到的序号来辅助我们解决数据冗余的问题,所以我们只需要一个倒计数定时器来帮助我们计算往返时延。
总结一下,在一个可靠的数据传输协议中,检验和、序号、定时器和肯定否定确认分组是至关重要的
1.2 流水线可靠数据传输协议
我们之前讲的几种协议中,不管是rdt1.0、rdt2.x还是rdt3.0,他们本质上都是一种停等协议。也就是说,一个相同的发送方不能在一次传输时间内同时传输多次报文,发送方会浪费大量的时间在往返时延上,导致了信道的利用率比较低,这肯定是不符合我们的需求的。因此我们采用一种流水线的技术,允许发送方同时发送多个数据分组而无需等待确认。
那么我们在流水线可靠数据传输协议中如何处理比特丢失、损坏和丢包的问题呢?
解决流水线差错回有两种基本方法:回退N步(GBN)和选择重传(SR)
回退N步协议(GBN)
流水线中是允许发送方同时发送多个分组的,但这个分组的数量是有限制的,一个最直接的原因就是流量控制。我们前面已经讲述了序号,而在GBN协议的序号范围中,那些已经被发送但还未被确认的许可序号范围可以被视作一个在序号范围内长度为N的窗口,这个N就是我们的窗口长度。在GBN协议中,采用的是累计确认的机制,接收方会按分组次序进行交付,如果第N个分组丢失,那么接收方会将丢掉第N个分组后面的并等待发送方重传,这里突出的特点就是分组的按序交付。发送方需要维护的数据有:窗口的上下边界、nextseqnum的位置;而接收方需要维护的信息就是下一个分组的序号。
优点:接收缓存简单,即接收方无需缓存失序分组。
GBN的序号范围被划分为四段:
[0,base-1]对应已经发送并确认的分组序号;
[base,nextseqnum-1]对应已经发送但还未被确认的分组序号;
[nextseqnum,base+N-1]对应未发送的分组序号;
[N,...] 不能使用的序号
选择重传(SR)
由于GBN中接收方采用的是无缓存分组机制,当出现丢包的问题而需要重传分组的时候,GBN就暴露了它的性能问题,当窗口长度较长以及需要重传的分组过多的时候,带宽的负载会很大,因此我们需要有选择性的去将分组进行重传,避免不必要的重传。
在选择重传协议GBN中,接收方发现缺少分组或者是分组失序后会将已有的分组进行缓存,等待发送方将缺失的分组传输并被正常接收后再将分组交付给上层。而发送方接收不到接收方的ACK后会选择重传分组。需要注意的是,发送方和接收方的窗口并不一定是一样的,这就要求SR协议窗口的长度必须小于等于序号空间的一半,窗口长度其实受到接收方接收和缓存报文的能力以及网络的拥塞程度决定的。
从上面的描述中我们知道,选择重传和GBN的一个显著区别就是SR会将分组进行缓存,而GBN则是将失序的分组丢弃。
二、TCP报文及数据传输
对于TCP协议我们其实并不陌生,TCP协议是一种面向连接的、可靠的数据传输协议,同时TCP连接是全双工的,即连接的两端可以互传数据。在深入了解TCP连接之前,我们先来弄清楚整个TCP连接的过程,之后在深入整个数据报文结构来认识TCP。
2.1 TCP连接
客户在传输数据报文之前会发送一个特殊的报文段,等待服务器返回一个特殊的报文段,在收到服务器返回的报文段后,客户再次向服务器端发送一个特殊报文段作为响应,这个过程其实就是TCP建立连接的过程,也称为三次握手阶段。需要注意的是,当客户再次发送特殊报文时是可以带上应用层数据的,而前两次的特殊报文段则不可以。
2.2 数据传输过程
一旦建立起来一条TCP连接,连个应用进程之间就可以相互传输应用数据了。这个过程:进程A通过套接字将数据流传递到运输层并在运输层进行封装成运输层的报文段,接下来由TCP控制将报文段放在TCP连接的发送缓存里,TCP连接会不时地将报文段从发送缓存中取出来,为其加上TCP首部形成TCP报文段并传递到网络层中。而网络层则是将TCP报文段分别封装在IP数据报里面并发送到网络中。当TCP另一端接收到一个报文段后就会将其放在接收缓存中等待读取。
需要注意的是,TCP存取报文段的数据数量受限于最大报文段长度(MSS)。而我们通常是基于本地发送主机发送地最大链路层帧长度(最大传输单元MTU)来设置MSS。
2.3 TCP的报文结构
与UDP一样,TCP报文的首部包括了源端口号、目的端口号和检验和字段。不同的是,TCP报文段的首部还包括序号和确认号字段、接受窗口字段、首部长度字段、选项字段和标志字段。我们先用一张图来大致了解TCP报文首部的结构。
几个参数功能:
- 序号字段:报文段的首字节的字节流编号
- 确认号字段:发起报文的接收方希望接收到发送方的下一字节序号
- 接收窗口字段:用于流量控制,16比特
- 首部长度字段:指示了32比特的字为档位的TCP首部长度,一般是20字节
- 选项字段:用于发送方和接收方协商最大报文长度(MSS),或者是在高速网络环境下作窗口调节因子。
- 标志字段:ACK用于指示确认字段中的值是有效的;RST、SYN、FIN比特用于TCP连接的建立和拆除;PSH比特被置位时,将指示接收方立即将数据交给上层;URG比特用来指示报文段中存在着被发送方上层定义为“紧急”的数据
需要注意的是,TCP是提供累计确认这一种机制的,即TCP只确认该报文流中至第一个丢失自己为止的字节序号。比如:接收方收到了0-300的字节,但没收到301-600的字节,那么它就会发送确认号为301的报文。
往返时间段的预估和超时时限的设置
TCP是如何预估往返时间并设置超时的实现的呢?我们首先要清楚,TCP不会抽取所有的样本,而是在我们成功建立的TCP连接中取一个不超时的样本来获取往返时间SampleRTT,同时会动态更新。 同时,TCP会对SampleRTT取一个均值EstimatedRTT,并利用公式:E=(1-a)*E+a*S 来获取往返时间。而对于超时时限,必须满足大于往返时间这个条件。
2.4 TCP的可靠数据传输
前面的学习中我们清楚,协议栈中位于运输层下面的网络层服务是不可靠的,而TCP就是在这种不可靠的服务上确保了进程中的数据流的无损坏、无间隙、非冗余和按序。同时,在TCP中,为了减少开销,定时器的管理过程我们任然使用单一的重传定时器。在TCP连接的过程中,发送方主要是完成三个动作:获取数据、定时器超时与重传、接收ACK,其中ACK是来自接收放的确认报文段。而对于接收方来说,如果收到的重传报文段中有冗余,则会丢失这些字节。
超时间隔加倍
在数据报丢失后,会激发TCP连接的重传机制。但我们需要考虑的的问题不只是能不能重传,还要清楚重传失败后超时时限会如何改变。其实在一次丢包事件中,每激发一次重传都会出现超时时限的加倍,这是基于网络在重传过程中的拥塞程度来考虑的。
快速重传
我们知道超时重传会增加我们的端到端的时延,所以我们不太可能使用将整个丢包后的字节进行重传,因此发送方在激发重传机制之前需要知晓丢包的具体情况。冗余ACK就是当发送方将丢包的报文段的下一序号报文段传递到接收方时接收方发送的ACK报文,指示着下一个期待的字节序号。当发送方接收到三个冗余的ACK后,就会执行快速重传的操作,也就是在定时器过期之前执行重传的操作,减少了端到端的时延。
现在来讨论TCP到底属于GBN还是SR:
其实TCP应该数G BN和SR的混合产物,相比于GBN不同的是,TCP会在接收方缓存已失序的报文段并等待丢失的报文。同时在重传机制中,GBN会将丢失的报文段N之后的所有保温进行重传,而TCP则根据确认号字段来重传丢失的报文段。
但是存在一个有趣的现象:
如果在N报文端出现丢包并计时器已经开始计时后,N+1的确认报文在计时结束之前到达发送端,TCP就不再会重传N报文段了。因此我们对此进行修改,选择确认的提出允许接收方有选择地确认失序报文段而不是确认最后一个正确接收的有序报文段。
流量控制
在前面的学习中,我们知道最高的传输效率就是发送速率=接受速率,当发送速率大于接受速率会导致TCP连接的接收缓存溢出,因此TCP提供了流量控制服务。该服务与拥塞控制比较相似但必须区分,TCP通过发送方维护一个接收窗口的变量来提供流量控制服务。接收方通过接收窗口告诉发送方自己的缓存空间大小,当缓存空间为0时,发送方只会发送只有一个字节的数据报文段到接收方,接收方会返回带有缓存空间大小数据的特殊报文给发送方。
2.5 TCP的连接与断开
在学习计算机网络之前,我们对于“三次握手”和“四次挥手”有所耳闻,其实这两个名词指的就是TCP连接过程中的连接与断开过程。在“三次握手”中TCP使用了两种特殊的报文段——SYN报文段和SYNACK报文段,分别对应发送方的发起连接和接收方响应连接的过程报文;在“四次挥手”中,使用的是FIN比特置一的报文段和ACK报文段。
三次握手
- 客户端的TCP首先向服务器端的TCP发送一个特殊的SYN报文段,其中SYN比特置为1,在这个阶段客户端会随机选择一个初始序号并将其封装在TCP首部中。
- 服务器接收到SYN报文段后会获取客户端的初始序号n并将n+1放在确认号字段中,同时选择一个初始序号放在序号字段中,该过程SYN比特仍为1。
- 客户端在接收到SYNACK报文后会将SYN比特置0,并向服务器主机发送报文段进行允许连接的确认。
四次挥手
当客户端想要结束TCP连接时候会向服务器端发送一个特殊的TCP报文段,该报文段的首部FIN比特置为1;同时接收FIN报文的服务器端会响应发起一个ACK确认该报文的收到,再发起一个首部FIN比特为1的特殊报文,客户端返回一个确认报文段,自此两台主机上的资源都会被释放。
三、拥塞控制
在TCP连接中丢包一般是在网络拥塞导致接收缓存溢出而导致的,丢包后频繁的分组重传操作一直是导致网络拥塞的主要原因,所以我们必须要有一种机制来一直抑制网络拥塞,直接的处理方法就是抑制报文发送方继续发送数据报,这其实就是拥塞控制。 在这一部分,我们会整理拥塞的代价、拥塞的两种控制方法和TCP的拥塞控制算法,首先我们来看一下拥塞控制的原因和代价。
3.1 拥塞代价
拥塞控制的代价有如下几种情况:
- 即使总吞吐量是一个理想的状态,但从时延的角度来看,却远不是理想的状态。也就是说,当分组的到达速率接近两路的容量速率时,分组会尽力一个较大的排队时延;
- 发送方必须执行重传以补偿缓存溢出而丢弃的数据分组;
- 发送方在经历较大的时延所进行不必要重传会引起路由器利用其链路带宽来转发不必要的分组副本;
- 如果以一个分组沿一条路径被丢弃是,上游的路由器用于转发该分组使用的传输容量就意味着被浪费掉了
3.2 拥塞控制方法
在最为宽泛的级别上我们根据网络层是否为运输层拥塞控制提供了显示的帮助来区分拥塞控制方法:主要是端到端拥塞控制和网络辅助拥塞控制。在端到端拥塞控制中,网络层并未对运输层拥塞控制提供显示支持,TCP报文段的丢失通过响应超时和是在三次连续的冗余ACK来确认,TCP主要通过减小拥塞窗口的长度来降低发送速率;而在网络辅助的拥塞控制中,路由器向发送方提供关于网络中拥塞状态的显示反馈信息,该反馈主要是通过一个比特来记录拥塞状态。拥塞信息从网络反馈到发送方通常有两种方式:一是直接由路由器采用阻塞分组的形式通知发送方;更为通用的是第二种,由路由器标记或者更新从发送方流向接收方的分组中的某个字段来指示拥塞的产生。
3.3 TCP的拥塞控制
宏观上了解了拥塞控制,我们来看看TCP中的拥塞控制机制——端到端拥塞控制,这是因为IP层不会像端系统提供显示的网络拥塞反馈。TCP的拥塞控制机制实现了每一个发送方能根据“感知”到的网络拥塞程度来限制发送流量的速率,这种感知其实就是检测网络拥塞。
3.3.1 如何检测拥塞?
我们在前面讲述可靠的数据传输的时候提及TCP发送方应对丢包方法——超时重传。当然了,为了减少端对端的时延,我们也同样选择了快速重传的机制(三次冗余ACK报文段)。因此,检测丢包事件我们只需要触发超时事件或者是发送方接收到三次冗余ACK,这时候发送方就认为通信链路出现了拥塞情况并及时减小发送速率。
3.3.2 如何改变发送速率?
在网络传输中,TCP会以确认报文为数据报正常到达的指示,同时确认报文到达发送方的速率其实就间接反映了网络罗的拥塞程度。如果确认报文正常到达,发送方将增加拥塞窗口的长度。拥塞窗口的增加速率与确认报文的到达速率成正比。因为TCP是通过确认来触发增大它的拥塞窗口长度,所以被称为自计时的。
3.3.3 拥塞控制算法
前面的梳理中还是没有讲清楚拥塞控制中发现丢包后如何降低发送速率,接下来跟着荔枝一起往下看。拥塞控制算法主要包括三个状态:慢启动、拥塞避免和快速恢复,其中慢启动和拥塞避免是必不可少的。我们把拥塞窗口长度记为cwnd,往下看这三个概念:
慢启动
在慢启动的状态下,cwnd以一个MSS开始,每当有一个确认报文段反馈回来就会增加一个MSS,所以从首次开始,一次RTT内发出的报文段呈倍数递增:1,2,4,8...。结束这种指数增长的情况有:一是超时丢包事件,TCP发送方会将cwnd重置为1并将状态变量ssthresh设置为cwnd/2;二是当变量ssthresh设置为cwnd/2时,如果cwnd等于ssthresh时,会结束慢启动并转移到拥塞避免模式;三是丢包后的快速重传并直接进入快速恢复状态。
拥塞避免
一旦进入拥塞避免状态,cwnd会变成原来的一半,并且在一个RTT内到达新的确认就只会增加一个MSS。这种线性增长在遇到超时后,与慢启动一样会将cwnd设置为一个MSS并将状态变量ssthresh设置为cwnd/2。如果时冗余ACK事件指示的丢包,反应程度不会那么剧烈,只是将cwnd减半并设置ssthresh值为cwnd/2,然后进入快速恢复状态。
快速恢复
在快速恢复状态中,对于引起TCP进入快速恢复状态的ACK报文段,对收到每一个冗余的ACK,cwnd都会增加一个MSS,当丢失报文段的一个ACK到达时,TCP会在降低cwnd后进入拥塞避免状态。如果出现超时事件,快速恢复在执行如同慢启动和拥塞避免相似的动作后会进入慢启动状态;当丢包事件出现时,执行如同慢启动和拥塞避免相似的动作。
总结
在这篇文章中,荔枝主要还是梳理了TCP的数据传输和报文结构,也详细讲述了对于TCP可靠性的几个考量参数,同时也补充了运输层拥塞控制算法了,本来想昨天结束运输层的,结果整理了两天才写完,进度好慢。。。总之,今天一定!!!
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~谢谢大家的支持嘻嘻嘻~~~ ♥比心心♥~~~😘