深入理解 TCP 的滑动窗口、快速重传、流量控制和拥塞控制机制

一、滑动窗口存在的意义

在前一篇博客中,我们介绍到了 TCP 的确认应答机制,当两台计算机之间建立连接之后,就可以进行传输数据了, TCP 每发送一个数据,都要进行一次确认应答,发送一个 ACK应答包,当上一个数据包确认应答了,再发送下一个,从而保证数据的可靠传输。

这种传输方式,虽然可靠但是缺点明显,每次只能发送一条数据,并且只有当收到 ACK确认包才能发送下一条数据,传输数据非常低下。

为了解决这个问题,TCP 引入了滑动窗口。

二、滑动窗口机制的实现

1、窗口的实质

窗口实际上是操作系统开辟的一块缓冲区,发送方发送数据将会将数据存放在缓冲区里,如果发送方收到接收方回复的确认应答,这个数据就会从缓冲区里面删除。但是,窗口不是发送缓冲区,也不是接收缓冲区。

2、滑动窗口机制的实现过程

1、基本过程

上一篇博客讨论到了确认应答策略,对每一个发送的数据报,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做会有一个比较大的缺点,就是性能较差,尤其是往返时间较长的时候。

既然这样一发一收的方式性能较低,那么我们一次可以发送多条数据(其实是将多个段的等待时间重叠在一起了),就可以大大的提示性能

  • 窗口大小是指舞曲等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段)
  • 发送前四个段的时候,不需要等待任何 ACK 应答,直接发送
  • 收到第一个 ACK 后, 滑动窗口向后移动,继续发送第五个段的数据;依此类推
  • 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉
  • 窗口越大,网络的吞吐量越大

那么如果出现了丢包,如何进行重传,分 2 种情况讨论

**情况一:**数据包已经抵达,但是 ACK 丢了

由于接收方缓冲区可以根据数据包报头的序号,对接收来的数据包按先后进行排序,使得接收方也可以按序读取。

这一操作使得接收方发送的ACK号 Y 表示 所有序号小于 Y 的数据都已被正确、按序接收。这也称为 TCP 的累计确认机制。所以在第一种情况下,部分 ACK 丢了并不要紧,因为可以通过后续的 ACK 进行累计确认。

情况二:数据包在传输过程中丢了,导致接收方没收到

当某一段报文段丢失之后,发送端会一直收到 1001 这样的 ACK,就像是在提醒发送端 "我想要的是 1001" 一样

如果发送端主机连续收到同一个 "1001" 的应答,代表只收到了 1001 之前的数据包,就会将对应的数据 1001 - 2000 重新发送

这个时候接收端收到了 1001 之后,再次返回的 ACK 就是 7001了, 因为 2001-7000 的数据包接收端气质之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中;

这种机制被称为 " 高速重发控制"(也叫"快重传")

**3、**TCP 滑动窗口的计算

TCP 滑动窗口是发送方实际能发送的数据量,它的的大小是一个动态调整的值,主要受 3 个因素控制:

  1. 接收窗口(rwnd)--- 接收方能力限制,由流量控制

  2. 拥塞窗口(cwnd)--- 网络状况限制

  3. 系统最大窗口 --- 协议/系统限制

最终窗口窗口大小 = min(rwnd, cwnd)

1、接收窗口 (Receiver Window, rwnd)

接收窗口是接收方根据自己缓冲区可用空间声明的窗口大小, 在 TCP 首部中, 有一个16位窗口字段,就是存放了窗口大小信息;16位数字的最大就是 65535 字节。实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是窗口字段的值左移 M 位。

接收窗口大小 = 接收缓冲区总大小(操作系统设置)- (已接收的最后字节序号 - 应用已读取的最后字节序号)

rwnd = receive_buffer_size - (last_byte_received - last_byte_read)

流量控制

接收方会根据本地缓冲区状态重新计算 rwnd ,并通过ACK包通告给发送方, 这一过程称为流量控制。

由于接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。

因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)

  • 接收端可以将自己可以接收的缓冲区大小放入 TCP 首部的 "窗口大小" 字段, 通过 ACK确认包通知发送端;
  • 窗口字段越大,说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接收到这个窗口后,就会减慢自己的发送速度
  • 如果接收端缓冲区满了,就会将窗口设置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

2、拥塞窗口(Congestion Window, cwnd)

这是 TCP 最复杂的部分,包含多个算法阶段,拥塞窗口的大小通过拥塞控制来调整

拥塞控制

虽然 TCP 有滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是由于网络上有很多计算机,可能当前的网络状态就已经比较拥堵,在不清粗当前网络状态下,如果在开始阶段就发送大量的数据,是有可能雪上加霜的

TCP 引入慢启动机制,先发少量的数据,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;

  • 此处引入一个概念为 "堵塞窗口"
  • 发送开始的时候,定义拥堵窗口大小为 1
  • 每次收到一个 ACK 应答,拥堵窗口加 1
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口作比较,取较小的值作为实际发送的窗口
RTT轮次 开始时cwnd (段数) 本RTT发送 数据包数量 本RTT将收到 ACK数量 ACK到达顺序 cwnd更新过程 (每次ACK到达时) RTT结束时 cwnd值 增长率
0 1 1 1 ACK#1 cwnd = 1 + 1 = 2 2 ×2
1 2 2 2 ACK#1 cwnd = 2 + 1 = 3 4 ×2
ACK#2 cwnd = 3 + 1 = 4
2 4 4 4 ACK#1 cwnd = 4 + 1 = 5 8 ×2
ACK#2 cwnd = 5 + 1 = 6
ACK#3 cwnd = 6 + 1 = 7
ACK#4 cwnd = 7 + 1 = 8
3 8 8 8 ACK#1 cwnd = 8 + 1 = 9 16 ×2
ACK#2 cwnd = 9 + 1 = 10
ACK#3 cwnd = 10 + 1 = 11
ACK#4 cwnd = 11 + 1 = 12
ACK#5 cwnd = 12 + 1 = 13
ACK#6 cwnd = 13 + 1 = 14
ACK#7 cwnd = 14 + 1 = 15
ACK#8 cwnd = 15 + 1 = 16
4 16 16 16 ACK#1-16 (每次+1,共加16) 32 ×2

像上面这样的拥塞窗口增长速度,是指数级别的,"慢启动" 只是指开始时慢,但是增长速度非常快

  • 为了不增长的那么快,因此不能使拥塞窗口简单的加倍
  • 此处引入一个叫做慢启动的阈值
  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
  • 当 TCP 开始启动的时候,慢启动阈值等于窗口最大值
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为是网络堵塞;

当 TCP 通信开始后,网络吞吐量会逐渐上升,当网络发生堵塞,吞吐量会立即下降

相关推荐
安科士andxe10 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
YJlio12 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
CTRA王大大13 小时前
【网络】FRP实战之frpc全套配置 - fnos飞牛os内网穿透(全网最通俗易懂)
网络
儒雅的晴天13 小时前
大模型幻觉问题
运维·服务器
testpassportcn13 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
通信大师14 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
Tony Bai15 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
消失的旧时光-194316 小时前
从 0 开始理解 RPC —— 后端工程师扫盲版
网络·网络协议·rpc
默|笙16 小时前
【Linux】fd_重定向本质
linux·运维·服务器
叫我龙翔17 小时前
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层结构
服务器·网络·c++·json