1. 滑动窗口原理
1.1 基本概念
滑动窗口是TCP实现高效可靠传输的核心机制,允许发送方在未收到确认的情况下连续发送多个数据包。
1.2 窗口的组成
**发送窗口结构**:
```
发送方视角:
|==========已确认==========|===============未确认===============|==============不可发送==============|
|<- 发送窗口大小 ->|
|<-已发送->|<-可发送->|
```
-
**已确认区域**:数据已发送且收到ACK
-
**已发送未确认区域**:数据已发送但未收到ACK
-
**可发送区域**:可以发送但尚未发送的数据
-
**不可发送区域**:超出窗口范围,暂时不能发送
**接收窗口结构**:
```
接收方视角:
|==========已确认==========|===============接收缓冲区===============|==============不可接收==============|
|<- 接收窗口大小(rwnd) ->|
|<-已接收->|<-可接收->|
```
-
**已确认区域**:数据已接收并交给应用程序
-
**已接收未确认区域**:数据已接收但未交给应用程序
-
**可接收区域**:可以接收的数据范围
-
**不可接收区域**:超出窗口范围,暂时不能接收
1.3 窗口滑动过程
**发送窗口滑动**:
```
初始状态:
发送窗口: [1,2,3,4]
发送数据1,2:
发送窗口: [*,*,3,4] (*表示已发送未确认)
收到ACK(3):
发送窗口滑动: [3,4,5,6]
```
**接收窗口滑动**:
```
初始状态:
接收窗口: [1,2,3,4]
接收数据1,2:
接收窗口: [*,*,3,4] (*表示已接收未确认)
应用程序读取数据1,2:
接收窗口滑动: [3,4,5,6]
```
1.4 实例:滑动窗口工作过程
**场景**:窗口大小为4个报文段
```
步骤1:发送方发送1,2,3
发送窗口: [*,*,*,4]
接收窗口: [*,*,*,4]
步骤2:接收方发送ACK(4), rwnd=4
发送窗口滑动: [4,5,6,7]
接收窗口滑动: [4,5,6,7]
步骤3:发送方发送4,5,6,7
发送窗口: [*,*,*,*]
接收窗口: [*,*,*,*]
步骤4:接收方发送ACK(8), rwnd=4
发送窗口滑动: [8,9,10,11]
```
2. rwnd接收窗口大小怎么算
2.1 计算公式
```
rwnd = 接收缓冲区总大小 - 已接收未确认的数据量
```
**公式含义**:
-
接收缓冲区总大小:系统为TCP连接分配的缓冲区大小
-
已接收未确认的数据量:已收到但应用程序尚未读取的数据量
-
rwnd:告诉发送方还能发送多少字节
2.2 缓冲区管理
**缓冲区分配**:
```
每个TCP连接有独立的接收缓冲区
默认大小通常为64KB或128KB
可通过内核参数调整:
-
tcp_rmem:接收缓冲区大小范围
-
tcp_wmem:发送缓冲区大小范围
```
**动态调整**:
```
操作系统根据网络状况动态调整缓冲区大小
当网络带宽高、延迟低时,增大缓冲区
当网络拥塞时,减小缓冲区
```
2.3 实例:rwnd计算过程
**场景**:接收缓冲区大小为8KB
```
步骤1:初始状态
接收缓冲区: 8192字节可用
rwnd = 8192
发送ACK, rwnd=8192
步骤2:接收2KB数据
已接收未确认: 2048字节
rwnd = 8192 - 2048 = 6144
发送ACK, rwnd=6144
步骤3:应用程序读取1KB数据
已接收未确认: 1024字节
rwnd = 8192 - 1024 = 7168
发送ACK, rwnd=7168
步骤4:接收3KB数据
已接收未确认: 4096字节
rwnd = 8192 - 4096 = 4096
发送ACK, rwnd=4096
```
3. 窗口关闭与糊涂窗口综合症
3.1 窗口关闭
**窗口关闭条件**:
```
当接收缓冲区满时,rwnd=0,表示窗口关闭
发送方收到rwnd=0后,停止发送数据
```
**窗口关闭场景**:
```
应用程序读取数据速度慢
网络传输速度快于处理速度
系统资源不足
```
**窗口关闭处理**:
```
发送方行为:
-
停止发送数据
-
等待窗口更新
-
启动零窗口探测定时器
接收方行为:
-
继续接收紧急数据(URG指针)
-
应用程序读取数据后发送窗口更新
```
3.2 糊涂窗口综合症
**问题描述**:
```
当接收方缓冲区只有少量空间时,发送小窗口通知
导致发送方发送大量小数据包
降低网络效率
```
**问题原因**:
```
接收方原因:
-
每次读取少量数据就发送窗口更新
-
导致rwnd很小
发送方原因:
-
只要有窗口空间就发送数据
-
发送大量小数据包
```
**实例**:
```
问题场景:
接收方每次读取1字节
每次发送ACK, rwnd=1
发送方每次发送1字节
结果:
产生大量41字节的TCP段(20字节IP头+20字节TCP头+1字节数据)
网络效率极低
```
**解决方案**:
```
接收方策略(延迟确认):
-
延迟窗口更新,直到有足够空间
-
仅当缓冲区有一半以上空间时发送更新
发送方策略(Nagle算法):
-
延迟发送,积累足够数据
-
除非收到ACK或数据达到MSS大小
```
4. 零窗口探测
4.1 探测目的
当接收窗口关闭(rwnd=0)时,发送方需要定期探测接收方是否有可用窗口。
4.2 探测机制
**探测流程**:
```
-
发送方收到rwnd=0
-
启动零窗口探测定时器
-
定时器到期后发送探测报文
-
接收方回复当前rwnd值
-
如果rwnd>0,继续发送数据
```
**探测间隔**:
```
初始间隔:3秒
指数退避:3秒→6秒→12秒→24秒→48秒
最大间隔:通常不超过60秒
```
**探测报文特点**:
```
-
数据长度为1字节
-
序列号为期望接收的下一个字节
-
不消耗发送窗口
-
使用PSH标志位
```
4.3 实例:零窗口探测过程
```
步骤1:发送方收到ACK, rwnd=0
发送方停止发送,启动定时器(3秒)
步骤2:定时器到期,发送零窗口探测
发送探测报文(1字节数据, seq=5000)
步骤3:接收方回复ACK(ack=5001), rwnd=2048
发送方收到更新,继续发送数据
如果未收到回复:
步骤4:等待6秒后再次探测
步骤5:等待12秒后再次探测
步骤6:超过最大次数则终止连接
```
5. 零窗口攻击
5.1 攻击原理
攻击者伪造rwnd=0的ACK报文,使发送方误认为接收方缓冲区已满,停止发送数据,从而造成拒绝服务。
5.2 攻击方式
**伪造ACK攻击**:
```
-
攻击者截获TCP连接的通信
-
猜测TCP序列号
-
发送伪造的ACK, rwnd=0
-
发送方收到后停止发送
-
合法通信中断
```
**持续攻击**:
```
攻击者定期发送零窗口通知
阻止发送方恢复发送
造成长时间服务中断
```
5.3 防御措施
**序列号验证**:
```
验证ACK序列号是否在合理范围内
拒绝序列号不在当前窗口内的ACK
```
**时间戳选项**:
```
使用TCP时间戳选项(RFC 1323)
时间戳单调递增
防止序列号回绕攻击
```
**异常检测**:
```
检测频繁的零窗口通知
识别异常的窗口大小变化
实施速率限制
```
**实例**:
```
攻击场景:
攻击者截获TCP连接
发送伪造ACK, rwnd=0
发送方停止发送
合法通信中断
防御场景:
接收方启用时间戳选项
发送方验证时间戳
拒绝伪造的ACK报文
连接正常进行
```
6. 总结
TCP滑动窗口和流量控制是保证可靠数据传输的关键机制:
-
**滑动窗口原理**:允许连续发送多个数据包,提高传输效率
-
**rwnd计算**:基于接收缓冲区大小和已接收数据量动态调整
-
**窗口关闭**:缓冲区满时暂停发送,等待窗口更新
-
**糊涂窗口综合症**:通过延迟更新和Nagle算法避免小数据包
-
**零窗口探测**:定期检查接收方窗口状态
-
**零窗口攻击**:通过序列号验证和时间戳防御
理解这些机制对于网络编程、性能优化和安全防护至关重要。在实际应用中,需要根据具体场景合理配置窗口参数,平衡传输效率和系统资源使用。