首页 > 其他分享 >TCP的可靠性如何保证

TCP的可靠性如何保证

时间:2023-09-23 18:35:18浏览次数:37  
标签:可靠性 窗口 重传 ACK TCP 发送 保证 数据包

TCP的可靠性之道:确认重传和流量控制 (qq.com)

面试被问TCP的可靠性是如何保证的? (qq.com)

image-20230923172410438

TCP 全称为 Transmission Control Protocol(传输控制协议),是一种面向连接的、「可靠的」、基于字节流的传输层通信协议,其中「可靠性」是相对于其他传输协议的优势点。

TCP 为了确保数据传输的可靠性主要做了以下几点:

  • 发送确认机制
  • 丢包重传机制
  • 滑动窗口
  • 拥塞控制

TCP 的传输基于字节流,记录起始序列号、是否发送、是否接收。

发送确认机制

TCP 报文头中有两个字段:

  • 「Sequence number」 序列号:表示要发送数据的起始号
  • 「Acknowledgment number」 确认号:表示消息已经接收,返回下次要发送的起始号

TCP 每次发送数据,都有一个确认应答 ACK,表示已经收到了数据包。确认号表示下一个传送的起始号。

图片

发送一个 http 请求,使用 Wireshake 抓取数据包,打开 Statistics -> Flow Graph,在弹出的页面上将 Flow type 修改成 TCP Flows,就能看到 TCP 的数据包请求:

图片

上图中标记了三个地方,中间的的标记的**「发送确认」**,就表示数据发送和确认应答,len 表示字节长度。发送 1 ~ 218 的字节,确认应答返回了确认号 219。

第二个发送确认也是类似原理,不同的是,这个发送确认时接收端的发送确认。

重传机制

发送端的数据包,一般都发送到接收端。但是在网络不好,或者信号比较差的情况,可能就无法正常发送到数据。

先介绍两个概念,「RTT」 和 **「RTO」**。

「RTT」 Round-Trip Time 表示往返时间,表示**「网络一段到另一端所需要的时间」**,也就是数据包的往返时间,以 TCP 握手为例:

图片

RTT 表示数据包从发送到收到确认应答的时间。

「RTO」 Retransmission Timeout 表示超时重传时间。超过这个时间没有确认应答,就会重传报文段,这个时间根据 RTT 来设置的。

重传机制是 TCP 基本的错误恢复功能,重传机制有这么几种:

  • 超时重传
  • 快速重传
  • sack
  • d-sack

超时重传

超时重传,字面意思是,超时规定的时间没有收到确认消息,就会再次发送一个消息请求。TCP 发送方发送报文时,会设置一个定时器,如果在时间范围内没有收到接收方发来的 ACK 确认报文,发送方就会重传已经发送的报文段。

TCP 有两种超时重传的情况:

  • 报文在发送途中丢失
  • 确认包在途中丢失

图片

图片

上面的 RTO 表示超时重传时间,RTO 的设定不能过大的或者过小:

  • 如果过大,请求等待的时间过长,请求的效率低。
  • 如果过小,正常返回的确认还未来得及返回,就重传。加大网络符合。

设置一个适当的 RTO 才会让重传机制更加高效。「超时时间 RTT 应该略大于往返时间 RTT」。

如果超时重传的报文段又超时了该怎么办呢?,答案就是「重传的超时时间加倍」,也就是再次超时重传的超时时间会增加到之前的两倍。

如果超时重传的报文段又丢包呢?此时发送方会以 RTO 时间的 2、4、8倍的倍数尝试多次重传。

超时重传如果消息多次没有收到确认报文,超时的周期也比较长,有没有更加高效的方法减少超时重传的时间呢?就引出下面的要讲的快速重传。

快速重传

快速重传不会等待超时时间到了再重传,发送方收到 3 次重复确认报文端,就不会等超时时间重试,而是直接重传报文。

图片

连续发送的报文段,中间只要有一个丢失,后续返回的确认号都是相同,后面的报文段无论有没有返回,都会重传一遍,这种设置还是比较合理的。在一段时间内,如果网络状况不好,导致丢包情况,后续的报文段一般也会丢包。

但是重传丢包后面所有的包,也会造成网络传输的浪费。对于上面的例子,如果只想传输 seq2,其他有返回的确认包就不用重传。

新的问题:发送端不知道重新发送seq2还是重新发送seq3、seq4、seq5、seq6,这种情况下不同版本的Linux有不同的实现。

sack

上面的问题抛出来了,Linux后面怎么解决呢,引入sack,Selective Acknowledgment 选择性重传。

这个字段的值会放在TCP头的选项字段上,就是发生上面例子中的情况下,后面接收端每次收到请求都会回复一个ACK和sack,这两个值中间的部分就是当前接收端缺失的数据,即ack<=x<sack。x就是缺失的那部分数据,这部分数据的序列号起始是ack,末端是sack-1,这样发送端就能知道接收端缺失的是哪部分数据,发送端只需要重新发送这些数据就可以了。

图片

d-sack

还有一种情况就是发送端发送的数据在网络中延时了,并没有丢失,那么在发送端进行重传后,这个延时的数据又到达了,这样就造成重复发送,这种情况下会采用d-sack方式,dsack其实就是利用sack处理重复数据的一种方式。依然是接收端回复ack+sack,只不过sack表示当前重复的这条数据的序列号,ack表示需要接受的序列号,这样发送端就能知道这条数据已经发送过了。

不难发现,可以通过ack和sack比较大小来区别这两种模式,如果ack大于sack就说明是数据重复发送了,如果ack小于sack就说明是数据缺失了。

图片

滑动窗口

TCP 发送比较大的数据包,TCP 会一次性发送大的数据包给接收方?答案是不会的,需要考虑网络带宽,「TCP 会将大的数据包拆分成多个大小适中的数据包」,发送一个 http 请求,添加较大的参数,使用 Wireshake 抓取数据包:

图片

上图中数据包被拆分成五个小的数据包。数据包被拆分成多个小的数据包之后,数据包发送都有返回一个确认序列号,每次发送一个新的包,都等待上一个包的 ACK 回来之后才能发送,这样一来一回的效率是很低的:

图片

TCP 为了解决这个问题,引入**「窗口」**的概念,在窗口范围内的数据包,无需等待上一次 ACK 确认,可以直接发送数据包:

图片

滑动窗口是 TCP 协议中的一种流量控制机制,用来控制发送方和接收方数据传输的速率,避免数据过多造成数据无法及时处理。

窗口的大小也就是 TCP 报文段的 window 字段,表示的就是接收方目前能接收的缓冲区的剩余大小,发送端根据这个字段处理发送的数据。

图片

发送端的窗口

发送窗口根据三个标准来划分:是否发送、是否收到 ACK、是否在接收方处理范围内,分成了四个部分:

图片

四个部分组成:

  • 第一部分是已经发送并收到 ACK 确认的数据,这部分数据已经发送成功了,无需在缓存中保留了。
  • 第二部分数据是已经发生但是未收到 ACK 确认的数据。
  • 第三部分数据是未发送,但是在接收方处理范围之内的数据。第二、第三部分共同组成发送的窗口。
  • 第四部分是需要发送,但是未在接收方范围之内的数据。这部分数据在没有接收 ACK 确认之前,是不会发送数据的。

如果发送方一直没有收到 ACK,数据不断的发送,很快可用窗口也被耗尽,这时发送方也不会继续发送数据了,这时发送端可用窗口为零的情况我们成为“零窗口”。

图片

随着 ACK 的确认,窗口也会依次向右滑动,比如发送端的窗口中,比如 40 ~ 43 字节都收到了 ACK 确认,那么整个可用的窗口就会顺次往右移动。此时 53 ~ 57的数据也都能发送了。

图片

接收端的窗口

接收端的滑动窗口相对发送的窗口要简单的多,主要分为三个部分:

  • 已经接收并确认的数据
  • 可以接收但是未接收的数据
  • 在接收范围之外(不够缓存的数据),也就是不可以接收的数据。

图片

但数据接收后,窗口也向右边滑动,给发生端的数据提供数据缓存。如果读取缓存的数据速度有变化时,接收端可能也会改变接收窗口的大小,以此来控制发送端的发送速度。这就是滑动窗口进行流量控制的一种机制。

在滑动窗口范围内发送端连续发送的几个数据包,如果中间有一个包的 ACK 丢失了,不一定需要重新发送,发送端可以通过下一个 ACK 确定丢失的这个 ACK 包需要不需要重发。也就是说有了滑动窗口的概念,当 ACK 回复包丢失后不一定需要重发。

发送端的窗口大小是由接收端决定的。

流量控制

滑动窗口的概念的提出,使得一次可以发送多个数据包,解决了一次只能处理一个数据包,效率低的问题。但是因为接收端的处理能力是有限的,作为发送端不能源源不断的给接收端发送数据,如果数据流量大了,接收端处理不了,就只能丢弃数据了,所以必须有一种机制可以控制数据的流量。

TCP 的流量控制也恰恰是基于滑动窗口的,滑动窗口由接收端确认后发送给发送端,发送端根据窗口大小进行发送数据,就能保证发送的数据在接收端都能被接收处理。

但是基于滑动窗口实现流量控制,TCP 考虑了这样几个问题:

滑动窗口是socket缓冲区中的一块空间,socket对应的缓冲区也不是一成不变的,所以如果缓冲区变化对滑动窗口的同步就会造成一些影响。比如接收端通知发送端窗口大小为100,但是此时操作系统把socket缓冲区减小了到了50,收到的数据大于50,就会把包丢掉就出现了丢包现象。

对策:

TCP 不允许同时减少缓冲区大小和窗口大小,如果需要减少缓冲区大小,必须先减少窗口大小,一段时间后再减少缓冲区大小。

还有一个问题,糊涂窗口问题,比如因为接收端处理比较慢,滑动窗口为0,窗口处于关闭状态,一段时间后,窗口出现了可能50个字节空闲空间,这时候就会把窗口=50通知发送端,发送端接收到窗口=50后,就会发送50字节的数据过来,但是要知道不管发送多少数据,TCP 都要给数据包上TCP 头,TCP 头就有20字节,同时还要包 IP 头,也是20字节,足足40字节,而发送的数据就只有10字节,性价比是极低的。而且还会占用带宽。

对策:避免接收端给发送端回复较小的窗口和避免发送端发送小的数据包。

TCP 规定接收端在窗口大小<min(mss,缓冲区的一半)的时候就要关闭窗口(回复接收端窗口为0),这样就能保证接收端不会发送小的窗口给发送端了。

发送端也要解决发送小数据的问题,发送端是通过nagle算法进行延迟处理,即满足以下两个条件中的一个才可以发送:

  1. 窗口大小大于 MSS 或者数据大小大于MSS
  2. 收到上一个数据发送回复的ACK

只要满足这两个中一个就可以发送。但是这个算法一旦开启就会造成一些数据本身就很小的包不能及时发送,所以这个算法开启要慎重考虑。

TCP 依靠上面的机制实现流量控制,具体的流程就是接收端每次都会给发送端回复窗口大小,当接收端很忙的时候,可能窗口就会变小到0,就会通知发送端窗口关闭,此时发送端就不会再发送数据给接收端,当接收端窗口变大后,就会主动回复发送端窗口大小。但是这个回复可能会丢失,那么这是就会出现互相等待的问题,可以理解为死锁,TCP 的解决方案是定义一个时钟,就是一个定时器,当到达一定时间接收端还没有通知窗口的话,发送端就会发送探测报文,一般每30-60秒发送一次,发送三次(这里不同实现可能不一样,可以配置),如果回复了窗口就开始发送数据,如果回复的窗口依然是0就重置时钟重新计时,如果最终都没有打开窗口,发送端可能会发送 RST 包给服务端终止连接。

拥塞控制

图片

网络中由于有大量的包传输,在固定带宽下处理不过来数据包的传输,可能会导致数据包阻塞,网络传输的速度下降,甚至会下降到 0 的情况。这就有点类似排队买东西,如果正常排队,速度虽然不快但处理速度比较稳定。但是如果一下涌来很多人口,就会处理不过来,导致**「堵死情况」**。

而 TCP 被设置成一个无私的协议,当遇到网络拥塞时,TCP 会减少自己发送数据包,这样网络拥塞会得到很大的缓解。

为了实现拥塞控制,首先在发送端定义一个拥塞窗口 CWND (congestion window),**「限制发送端发送数据最多没有收到 ACK 确认包的大小,超过拥塞窗口范围后,就不会继续发送数据了」**。

拥塞窗口会随着网络情况的变化动态的调用自身的大小,大体的变化规则是:如果没有出现拥塞,就扩大窗口大小,否则就缩小窗口的大小。

拥塞控制算法主要包含四个部分:

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复

慢启动

当一个新的TCP连接开始时,无法确定是否用拥塞发生,一开始不会发送大量的包,而是从最小的发送窗口开始,后续会采用倍增的方式增加窗口的大小,窗口大小从 1 开始,后续慢慢增大到 2、4、8 等。

图片

指数增加速度会越来越快,窗口扩大的一定的程度,就会减慢增加的速度,改成线性增加,这时候就进入拥塞避免阶段。

拥塞避免

慢启动和拥塞避免的临界点叫做**「慢启动门限」** ssthresh (slow start threshold)。

  • cwnd < ssthresh 时,使用慢启动算法。
  • cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

ssthresh 大小一般是 65535 字节。拥塞避免的规则是:「每当收到一个 ACK 时,cwnd 增加 1/cwnd」。就变成线性增长了。

图片

拥塞发生

拥塞避免将原来的指数增长改成了线性增长,虽然增长速度减慢,但 CWND 窗口还是在增长阶段。随着窗口进一步缓慢增加,网络还是会遇到阻塞的状态,会出现丢包的情况。就需要对丢包进行重传。

重传机制有两种:

  • 超时重传
  • 快速重传

当发生超时重传时,sshresh 和 cwnd 的值会发生如下变化:

  • sshresh 变成 cwnd 的一半
  • cwnd 重置为 1

cwnd 重置为1,表示直接进入慢启动状态。


上面的超时重传速度变化太快,而快速重传是一个相对温和的方案。

如果我们连续 3 次收到同样序号的 ACK,包还能回传,说明这个时候可能只是碰到了部分丢包,网络阻塞还没有很严重,无需重置 cwnd。

此时 ssthresh 和 cwnd 变化如下:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd

并进入到快速恢复阶段。

快速恢复

快速恢复主要是将 cwnd 恢复到正常大小,上面说的 cwnd 设置成原来的一半,ssthresh 设置成 cwnd 的大小。

快速恢复算法如下:

  • 重传丢失的数据包。
  • 如果接收到重复 ACK 确认,cwnd 增加 1。
  • 如果接收到新数据的 ACK 确认,就将 ssthresh 恢复到慢启动时期的值,因为返回新数据的 ACK 确认,表示网络阻塞已经结束,可以恢复到之前的状态,cwnd 也可以指数或者线性增加。

总结

TCP 提供基于字节流、可靠的数据传输,为了确保数据的可靠性,做了很多工作:

  • 报文段序号和确认号

    • 每个报文都有序号和确认号,序号表示报文段第一个字节号,确认号表示下一个接收字节的序号。
  • 发送确认和重传机制

    • 每个报文段发送后,都会确认应答 ACK,表示已经报文段已经成功发送。
    • 当网络异常数据包无法达到时,就会触发重传机制。重传主要有两种方式:超时重传和快速重传。
    • 超时重传:设置一个定时器,超过时间未收到确认应答,就会重新传数数据包。这个重传方式周期比较长。
    • 快速重传:快速重传不会等待超时时间到了再重传,是以数据为基点,发送多次报文段,当接受到重复的确认应答号 ACK 时,直接重传所有的报文段。可以使用 SACK 记录哪些报文段已经成功接收了,只重传没有被成功接收的报文段。
  • 滑动窗口

    • 报文段拆分,TCP 将要发送的数据拆分适当大小的数据包。
    • 引入窗口的概念,这个窗口大小是由接收方来决定,表示接收方可以接收的缓存大小。在窗口范围之内, TCP 可以连续发送多个数据包给接收方,当数据包发送并且有确认应答,整个窗口会往后移动,继续发送新的数据。
    • 随着数据传输的速度和网络情况,接受方可能会动态修改窗口的大小,以此来控制数据传输的速度。
    • 滑动窗口能流量进行控制,控制数据发送的速度和频率,避免出现拥塞情况。
  • 拥塞控制,在网络传输中可能会出现大量的数据请求,而固定的网络宽带可能处理不过来这么多数据传输,容易形成阻塞的情况。TCP 遇到网络拥塞时,会自动减少自己发送包的数量,这样网络拥塞情况就会缓解。TCP 发送端定义拥塞窗口 CWND,表示没有接收到 ACK 确认数据的最大发送量。拥塞控制算法主要包含四个部分:

    • 慢启动:开始一个新的连接时,从较小的发送窗口开始,然后**「指数增长」**增加 CWND 窗口大小,知道达到慢启动门限。
    • 拥塞避免:窗口达到慢启动门限临界点时候,慢启动阶段结束,这个阶段,窗口大小**「线性增加」**,增长速度比较慢,避免发生网络拥塞。
    • 拥塞发生:窗口进一步缓慢增加,网络还是会遇到阻塞的状态,会出现丢包的情况。就需要对丢包进行重传。此时有两种重传机制:超时重传和快速重传。超时重传,是窗口大小重置为 1,数据传输又恢复成慢启动时的速度。这种传输速度急剧下降,不利于系统稳定,由于窗口大小限制,网络传输次数更多,拥塞的情况也会更大。而快速重传是相对温和的方案,此时认为网络只是暂时有阻塞情况,将窗口大小 CWND 改成原来的一半,并进入快速恢复阶段。
    • 快速恢复:重传丢失的数据包,如果接收到重复 ACK 确认,cwnd 增加 1。如果接收到新数据的 ACK 确认,就将 ssthresh 恢复到慢启动时期的值,因为返回新数据的 ACK 确认,表示网络阻塞已经结束,cwnd 也可以指数或者线性增加。

标签:可靠性,窗口,重传,ACK,TCP,发送,保证,数据包
From: https://blog.51cto.com/coderge/7580077

相关文章

  • Kafka的消息传递保证和一致性
    前言通过前面的文章,相信大家对Kafka有了一定的了解了,那接下来问题就来了,Kafka既然作为一个分布式的消息队列系统,那它会不会出现消息丢失或者重复消费的情况呢?今天咱们就来一探。实现机制Kafka采用了一系列机制来实现消息传递的保证和一致性,关键点:至少一次的消息传递(AtLeastOnceD......
  • 消息队列中,如何保证消息的顺序性?
    本文选自:advanced-java作者:yanglbme问:如何保证消息的顺序性?面试官心理分析其实这个也是用MQ的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的?这是生产系统中常见的问题。面试题剖析我举个例子,我们以前做过一个mysqlbinlog同步的系统,压......
  • 消息队列中,如何保证消息的顺序性?
    本文选自:advanced-java作者:yanglbme问:如何保证消息的顺序性?面试官心理分析其实这个也是用MQ的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的?这是生产系统中常见的问题。面试题剖析我举个例子,我们以前做过一个mysqlbinlog同步......
  • Kafka的消息传递保证和一致性
    前言通过前面的文章,相信大家对Kafka有了一定的了解了,那接下来问题就来了,Kafka既然作为一个分布式的消息队列系统,那它会不会出现消息丢失或者重复消费的情况呢?今天咱们就来一探。实现机制Kafka采用了一系列机制来实现消息传递的保证和一致性,关键点:至少一次的消息传递(AtLeas......
  • java中如何保证数据库数据的一致性
    本文使用的数据库是mysql一、不考虑并发时的写法假设现在有一张t_product表,我们先只考虑单实例部署时的情况CREATETABLEt_product(idINTPRIMARYKEY,NAMEVARCHAR(50),numsINT);INSERTINTOt_product(id,NAME,nums)VALUES(1,'aa',1);我们现在有一个加库存的接口......
  • ETHERCAT转MODBUS TCP/IP协议网关
     ETHERCAT转MODBUSTCP/IP协议网关   产品介绍JM-ECT-TCPIP是自主研发的一款EtherCAT从站功能的通讯网关。该产品主要功能是将EtherCAT网络和TCP/IP网络连接起来。本网关连接到EtherCAT总线中做为从站使用,连接到TCP/IP网络中做为服务器或客户端使用。......
  • TCP连接的关键之谜:揭秘三次握手的必要性
    TCP连接建立当我们浏览网页、发送电子邮件或者进行在线游戏时,我们常常不会想到背后复杂的网络连接过程。然而,正是这些看似不起眼的步骤,确保了我们与服务器之间的稳定通信。其中最重要的步骤之一就是TCP连接的建立,而其中的核心环节就是三次握手。本文将详细探讨三次握手的原理、......
  • 使用TCP 创建服务器 多个客户端连接
    源码#include<stdio.h>#include<string.h>#include<unistd.h>#include<unistd.h>//close#include<sys/socket.h>//socket#include<arpa/inet.h>//inet_pton#include<netinet/in.h>//sockaddr_inintmain(){//1、......
  • tcpdump后台不间断抓包
    版本1的抓包命令这两天排查一个小问题,需要在服务器上使用tcpdump24小时不间断抓包,这里简单记录下。先看下tcpdump的语法:tcpdump[-AbdDefhHIJKlLnNOpqStuUvxX#][-Bbuffer_size][-ccount][-Cfile_size][-Grotate_seconds][-F......
  • HTTP之下的TCP做了什么?抓包解释!
    理清HTTP之下的TCP流程,让你的HTTP水平更上一层(qq.com)首先,我们准备这样一段服务端代码:constexpress=require('express')constapp=express()app.get('/',function(req,res){res.setHeader('Connection','close')res.end('hell......