TCP如何实现可靠传输/保证消息的顺序
TCP如何实现可靠传输
重传机制
超时重传
- 在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确定应答报文,就会重发该数据;也就是以时间为驱动;
- 可以通过滑动窗口减少超时重传的次数;
超时重传时间RTO的值应该略大于报文往返RTT的值
- 当RTO较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
- 当RTO较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多超时,更多超时导致更多重发;
超时重传时间RTO是一个动态变化的值
- 每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。
- 两次超时就说明网络环境差,不宜频繁反复发送;
这里动态变化让我联想到自旋锁的自旋时间也是动态变化的,参考:https://blog.nowcoder.net/n/66c68628942f4c1eabc0aff228a2408e
快速重传
以数据驱动重传、虽然解决了超时时间的问题,但是仍然面临者重传的时候,是重传之前的一个,还是重传所有问题;
SACK方法
主要告诉发送方哪些TCP段需要被重发;
- 需要在TCP头部【选项】字段里面添加一个SACK的东西,它可以将缓存的地图发送给发送方,这样发送方就知道了哪些数据收到了,哪些没收到;就可以只重传丢失的数据;
- 发送方收到三次同样的ACK报文就会触发快速重传机制,通过ACK信息发现只有某段数据丢失,重发时就会只选择这段TCP进行重发;
D-SACK方法
主要告诉发送方哪些TCP段被重复接收了;也就是SACK的区间小于ACK携带的数值,说明此段数据段是重复的;
- ACK丢包:触发了超时重传;ACK不断叠加,因为接收方已经接受到了;但是一直没有返回ACK所以发送方再次发送,发现SACK的区间小于ACK的数值说明此段数据是重复的;
- 网络延迟:触发了快速重传;ACK由于中间某一段TCP没有接收到,一直返回的是原来携带的数值;当触发快速重传之后被网络延迟的TCP端到达接收方,发现SACK的区间小于ACK的数值说明此段数据是重复的;
补充:TCP头部
滑动窗口
前提介绍
- TCP发送每一段数据,都需要进行一次确认应答。当上一个数据包收到了应答,再发送下一个;效率较低;
- 所以引入窗口的概念,无需等待确认应答,而可以继续发送数据的最大值;
概念
- 窗口大小:接收端告诉发送端自己还有多少缓冲区可以接受数据,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来;
- 窗口的大小通常是由接收方的窗口大小决定的;
过程
- 某个ACK丢失、可以通过下一个确定应答进行确认,因为ACK的值是累积的,后面的ACK的数值表示之前的所有数据都已经被接受到;
- 之后接受一个ACK,滑动一段区间、这个区间:将ACK之前所有未确定收到ACK的数据的范围进行滑动
流量控制
TCP提供一种机制可以让发送方根据接受方的实际接受能力控制发送的数据量
拥塞控制
概念:
- 避免因为重传机制造成发送方的数据填满整个网络;
- 拥塞窗口是发送方维护的一个状态变量,根据网络的拥塞程度动态变化的;
- 如果网络中出现了阻塞,cwnd就减少;如果网络中没有出现阻塞,cwnd就会增加;
- 如果发生了超时重传,就会认为网络中出现了拥塞。
拥塞控制的四种算法:
两种情况:
- 慢启动 --》拥塞避免 --》 拥塞发生 --》快速重传 --》 快速恢复 --》 拥塞避免
- 慢启动 --》拥塞避免 --》 拥塞发生 --》超时重传 --》 慢启动
慢启动:
- 当发送方每收到一个ACK,拥塞窗口cwnd的大小就会加1。
- 呈现指数型增长
当到达慢启动门限(ssthresh)的时候,就会触发拥塞避免
拥塞避免:
- 每收到cmdn窗口大小的ACK,拥塞窗口才会加一
- 呈现线性增长
当触发超时重传的时候就会进入拥塞发生算法
拥塞发生
超时重传:
- cwnd会重置为1;
- 慢启动门限(ssthresh)设为cwnd / 2;
- 之后重新回到慢启动的过程
快速重传:
- cwnd设置为cwnd / 2(原来的一半);
- 慢启动门限(ssthresh)设置为cwnd;
- 进入到快速恢复算法
快速恢复:
- 如果收到重复的ACk,那么cwnd增加1;
- 如果收到新数据的ACK后,会将cwnd设置为第一步中的ssthresh的值,因为ACK确认了新的数据,说明阻塞的ACK数据已经收到,恢复到之前的状态了,即将再次进入拥塞避免状态;
TCP如何保证消息的顺序
概述:
- 每个数据包会被分配一个序列号,接受方按顺序接受并确认。
过程:
- 主机每次发送数据时,TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收主机对分配的这个序列号进行确认,如果发送主机在一个特定时间内没有收到接收主机的确认,则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否丢失或者乱序等,接收主机一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。
具体步骤:
- 为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;并为每个已发送的数据包启动一个超时定时器;
- 如果在定时器超时之前收到对方发来的应答信息(可能是对本包的应答,也可以是对后续包的应答),则释放该数据包占用的缓冲区;
- 否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止;
- 接受方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累积应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可以在数据包中捎带过去。
hshuo的面试之路 文章被收录于专栏
作者目标是找到一份Java后端方向的工作 此专栏用来记录从Bilibili、书本、其他优质博客上面学习的内容 用于巩固、总结内容 主要包含Docker、Dubbo、Java基础、JUC、Maven、MySQL、Redis、SpringBoot、SpringCloud、数据结构、杂文、算法、计算机网络、操作系统、设计模式等相关内容