目录
流量控制
引入
--tcp协议介绍,协议段格式(端口号,首部长度,窗口大小,序号,确认序号,6个标志位),流量控制,确认应答机制,捎带应答,三次握手的双方认知不一致问题-CSDN博客
在之前的博客里,我们已经介绍过,可以根据对方主机的缓冲区大小动态控制发送速度,用来防止大面积丢包
那该如何保证第一次发送数据的大小的合理性呢?
协商接收能力
其实在三次握手期间,就已经协商了双方的接收能力
- 别忘了三次握手就是在交换报文,不仅里面携带的标志位是重点,其他字段同样也是
- 而且三次握手中的第三次,是可以携带数据的(捎带应答),因为一旦第三次成功,连接也就建立成功,通信本来就是顺理成章的事情 -- 这样可以提高通信效率
- 说明在前两次的时候已经协商好了
发送方式
发送报文可以串行发送,也可以并行发送
当然也可以混用
- 并行 -- 网络信号好
- 串行 -- 网络不好/需要进行管理动作
- 根据网络情况/通信情况动态调整
为什么要分次发送
其实,仔细想想,为什么要把这些数据分成一段一段的,打包成一个发过去不好吗,为什么要并行发送那么多个?单次io的效率还更快
- 因为硬件--也就是网卡,不允许发送大块数据
窗口探测
引入
发送过程中,如果对方的接收窗口大小变为0
- 根据流量控制,a主机就停止发送,等待对方的窗口大小更新(也就是等待对方应用层读取数据)
但是也不能一直等吧,所以有了窗口探测的机制
介绍
发送方
如果对方一直没有发送窗口更新通知,就会向对方发送一个窗口探测的包
- 而tcp报文一定要确认,也就是返回应答,那么应答里一定携带报头->窗口大小字段
- 这个过程中的报文只是一个报头,不需要携带数据,也就不存在因缓冲区不足而丢包的问题
如果网络出问题了,可能窗口探测报文无法成功传输
所以,不止发送方有策略,接收方也有相应的机制
接收方
b主机可以发送更新通知给对方(这里的报文也只是一个报头)
- 可以更好地提高效率
如果这个问题只是单向的,那么更新通知就可以成功到达
总结
总之,这样双方都有相应的策略,就会有先到达的报文
- 这样可以提高效率
- 也可以防止出现单向网络出现问题,导致报文发不过来的情况,这是一种容错性
并且,这两种机制:
- 窗口探测 -- 接收方的应答就是一种确认
- 窗口更新通知 -- 发送方收到后会立即发送报文,相当于是通知的应答
- 所以,这两种报文都有应答,可以确保对方是否收到
如果两个应答都没有收到
- 就证明双方网络有问题
- 重传一段时间后,连接就因异常直接退出
窗口大小
tcp中窗口大小字段为16位
- 也就是说,窗口大小最大为2^16,大概是64k
而窗口大小用来描述接收缓冲区的剩余空间
- 那么接收缓冲区最大就是64k?
- 不一定,因为tcp首部40字节中的选项里,还包含了窗口扩大因子M
最终,实际的窗口大小为
- 窗口大小字段的值<<M位,也就是窗口大小字段值 * 2^M
- 所以,实际的大小取决于系统实现(是否设置扩充因子和是否支持)
总结
流量控制属于可靠性的一种
- 防止正常报文的丢包
- 同时也提高了效率 -- 不丢包->不重传->不使用过多的网络资源
- tcp很多机制的设计都是这样
- 比如:捎带应答,既保证了可靠性,又提高了效率
滑动窗口
引入
因为存在并行发送
- 那么暂时没有收到应答的报文,需要tcp保存起来(方便之后的重传)
那势必这样的报文不止一个,这些报文被保存到哪里了呢?
- 其实不需要单独再找个地方存放
- 这些报文本来就是从tcp的发送缓冲区里拷贝到底层,让底层发送的
- 我们只需要对这个缓冲区做一个区域划分 -- 已发送已确认,已发送未确认,待发送
介绍
区域介绍
已发送已确认
- 可被上层覆盖,如果被覆盖,就相当于从tcp缓冲区中移除了
- 计算机里不存在真正的清空,只要这块区域可以被覆盖,就说明这块区域数据是无效的
已发送未确认
- 这块的报文不一定都发送了,是可以发 / 已经发,但尚未收到应答的区域
- 如果收到应答,就将该报文纳入到已发送已确认的区域中
- 如果还未收到,暂时不能被修改,这就相当于是在暂时保存了
- 这块区域就叫做滑动窗口 -- tcp发送缓冲区的一部分
这部分的数据是可以直接被推送给接收方的
而之前说的并发发送,正是因为有滑动窗口才能实现(需要一块区域作为暂存区,也需要一块区域来容纳可以直接发送的数据)
区域划分如何实现
缓冲区无非就是个数组
- 区域划分直接通过数组下标实现
- 很像我们的双指针算法,也像之前在虚拟地址空间里介绍的如何实现区域划分的一样,维护两个整数->一个区域
- 窗口的滑动,本质上就是指针移动
窗口如何移动
一旦有报文被确定了
- start就向右移动若干位
如果对方有更大的接收窗口了
- end就向右移动
原则上来说,窗口越大,网络吞吐率越高
滑动窗口会不会向左移动
原则上不可以
- 因为左侧是已确认已应答的,它没法把[已经应答过的数据]纳入[已发送未应答/可发送]的范畴
- 而右侧是待发送,它没法把[可以立即发送的/已发送的]又重新纳入到[待发送区]
接下来看看异常情况时,滑动窗口是如何处理的
异常情况的处理
应答丢失
如果发送了1000-5000的数据,其中2001-3000数据的应答丢失
- 只是应答丢失,说明数据还是收到了
- 那么收到的确认序号应该是5001 -- 确认序号允许少量应答的丢失
- 所以滑动窗口的start直接右移到5001
那如果最后一个报文的应答丢失了怎么办
- 因为我们之前都是依靠最后一个应答的确认序号,来判断哪些数据收到了
- 那确认序号最大只能是4001,所以需要补发4001-5000的数据
- 所以滑动窗口的start只能右移到4001(它指向最小序号的丢失报文)
如果应答全部丢失
- 就只能全部补发,因为没有确认序号让我们判定
总之,应答丢失我们是可以正确更新指针的
数据丢失
假设还是2001-3000的数据丢失,其他报文都收到了+有应答
- 那么收到的确认序号是:2001,2001,2001
- 所以,需要补发2001之后的数据
总结
总之,根据确认序号的特性 -- 确认序号之前的数据都已经收到了
- 能保证滑动窗口是线性的连续向后更新,不会出现跳跃
- 所以,确认序号的值可以正确指导滑动窗口的滑动
快重传
介绍
当出现丢包后,发送方可能会收到大量确认序号相同的应答:
如果收到3个同样的,就进行重发
- 发送方可以通过确认应答的值判断出要重发的数据
重发后
- 如果后面的报文都已经收到了,则应答的确认序号直接变成下一个数据包的序号:
- 如果还有报文丢失,则更新为该丢失报文的序号:
- (我们可以理解成接收方会有自己的方式记录下到底哪些收到了,哪些没收到,最终以确认序号的方式告诉对方)
- 如果同一个确认序号累计出现三次后,继续进行补发
以上机制叫做快重传
和超时重传的对比
有了快重传,为什么还要有超时重传?
- 需要保证可靠性
因为快重传是有条件的
- 必须收到三个相同的才会触发
- 如果一次没发送那么多数据,却有报文丢失怎么办 / 丢失的数据是倒数第二个/最后一个
- 如果没有其他策略,这个报文丢失了就补不回来了
快重传是一种提高效率的策略
- 可以让丢失的报文尽快重传,而不是等到超时再说
超时重传是用来兜底的
- 没有触发快重传的丢失的报文,都会触发超时重传
总结
指针的值如何确定
这里就把滑动窗口的开始和结束认为是数组下标
start = 确认序号
- 因为序号是一直在增长的,所以滑动窗口一直在右移
end = 确认序号 + min( 对方接收窗口大小,有效数据大小)
-- 注意,这里并不全面,在下一篇博客的"拥塞控制"中会介绍
- 为什么要找到最小值呢?
- 因为如果滑动窗口移着移着越界了怎么办?
- 缓冲区本质上是个数组,也就有对应的范围
- 而且,缓冲区其实不止三部分,在待发送区域的后面可能还有一部分区域,是还没有写入数据的部分,所以需要以是否是有效数据为界限
是否会越界
那它最终会越界吗?
- 会的
- 所以tcp采用了类似环状算法
缓冲区可以看作是基于数组的环形结构
- 物理上是线性结构
- 逻辑上是环形结构
当滑动窗口将要移动至缓冲区末尾时
- end便更新为0
- 刚好首尾部分(已确认区,未写入数据区)都是可写的,当上层写入数据时,如果缓冲区被打满了,就接着从起始位置写
滑动窗口与流量控制的关系
因为滑动窗口内存放的是可以直接给对方发送的数据
- 所以通过滑动窗口大小的控制 -> 控制发送数据量的多少
- 这也就实现了流量控制
流量控制不只是可以减缓发送速度,也可以增大
- 取决于对方的接收能力 -> 调整滑动窗口 -> 控制发送数据量