深入理解 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 通信开始后,网络吞吐量会逐渐上升,当网络发生堵塞,吞吐量会立即下降

相关推荐
承渊政道35 分钟前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
讯捷蓝达3 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
数字新视界5 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落5 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
lisanmengmeng6 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
严肃小白兔6 小时前
Cloudflare 更新 API Token 管理:从最小权限到自动化凭据安全
网络·安全·自动化
Android系统攻城狮6 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
上火的金鱼妹6 小时前
k8s生产实际问题排查(网关,数据库读写等)
运维·服务器
Linux-lucky18 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
爱莉希雅&&&21 小时前
GitLab 完整学习笔记
linux·运维·服务器