📄 docs: 添加游戏加速器完整技术方案(14部分)
This commit is contained in:
@@ -0,0 +1,641 @@
|
||||
# 第四部分:流量转发系统设计
|
||||
|
||||
## 4.1 MVP阶段 — UDP Relay
|
||||
|
||||
### 4.1.1 协议设计
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ UDP Relay 协议报文结构 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 报文头 (16字节): │
|
||||
│ ┌────────┬────────┬────────┬────────┬────────┬────────┐ │
|
||||
│ │ Magic │ Version│ Type │ Flags │ SeqNum │ Length │ │
|
||||
│ │ 2B │ 1B │ 1B │ 1B │ 4B │ 4B │ │
|
||||
│ │ 0xACE0 │ 0x01 │ │ │ │ │ │
|
||||
│ └────────┴────────┴────────┴────────┴────────┴────────┘ │
|
||||
│ │
|
||||
│ 字段说明: │
|
||||
│ ┌──────────┬──────────┬────────────────────────────────────┐ │
|
||||
│ │ 字段 │ 大小 │ 说明 │ │
|
||||
│ ├──────────┼──────────┼────────────────────────────────────┤ │
|
||||
│ │ Magic │ 2 bytes │ 魔数 0xACE0,用于识别协议 │ │
|
||||
│ │ Version │ 1 byte │ 协议版本号(当前0x01) │ │
|
||||
│ │ Type │ 1 byte │ 报文类型 │ │
|
||||
│ │ │ │ 0x01=DATA, 0x02=ACK, 0x03=HEARTBEAT │ │
|
||||
│ │ │ │ 0x04=CONTROL, 0x05=FEC │ │
|
||||
│ │ Flags │ 1 byte │ 标志位 │ │
|
||||
│ │ │ │ bit0: 加密 │ │
|
||||
│ │ │ │ bit1: 压缩 │ │
|
||||
│ │ │ │ bit2: FEC │ │
|
||||
│ │ │ │ bit3: 重传 │ │
|
||||
│ │ │ │ bit4-7: 保留 │ │
|
||||
│ │ SeqNum │ 4 bytes │ 序列号(用于乱序检测和重传) │ │
|
||||
│ │ Length │ 4 bytes │ 载荷长度 │ │
|
||||
│ └──────────┴──────────┴────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 扩展头(可选,当Flags中有加密/压缩时): │
|
||||
│ ┌────────┬────────┬────────┬────────┬────────┐ │
|
||||
│ │ ExtLen │ ExtType│ IV │ Padding│ HMAC │ │
|
||||
│ │ 1B │ 1B │ 12B │ 可变 │ 16B │ │
|
||||
│ └────────┴────────┴────────┴────────┴────────┘ │
|
||||
│ │
|
||||
│ 完整报文: │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ 报文头 │ 扩展头 │ 加密载荷 │ HMAC校验 │ │
|
||||
│ │ 16B │ 可变 │ 可变 │ 16B │ │
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.1.2 数据流转时序图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ UDP Relay 数据流时序图 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 玩家客户端 接入节点 中转节点 游戏服务器│
|
||||
│ │ │ │ │ │
|
||||
│ │ 1.游戏UDP包 │ │ │ │
|
||||
│ │─────────────────→│ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 2.封装为 │ │ │
|
||||
│ │ │ Relay包 │ │ │
|
||||
│ │ │ (加序列号) │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 3.加密+转发 │ │ │
|
||||
│ │ │───────────────→│ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ 4.解密+解封装 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ 5.转发原始包 │ │
|
||||
│ │ │ │─────────────────→│ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ 6.游戏响应 │ │
|
||||
│ │ │ │←─────────────────│ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 7.封装+加密 │ │ │
|
||||
│ │ │←───────────────│ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ 8.解密+解封装 │ │ │ │
|
||||
│ │←─────────────────│ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ 9.返回游戏 │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │
|
||||
│ 总延迟增加:2-10ms(取决于节点间距离) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.1.3 伪代码实现
|
||||
|
||||
```go
|
||||
// UDP Relay 核心转发逻辑
|
||||
package relay
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 协议常量
|
||||
const (
|
||||
Magic = 0xACE0
|
||||
Version = 0x01
|
||||
TypeDATA = 0x01
|
||||
TypeACK = 0x02
|
||||
TypeHEART = 0x03
|
||||
TypeCONTROL = 0x04
|
||||
TypeFEC = 0x05
|
||||
HeaderSize = 16
|
||||
)
|
||||
|
||||
// RelayHeader 报文头
|
||||
type RelayHeader struct {
|
||||
Magic uint16
|
||||
Version uint8
|
||||
Type uint8
|
||||
Flags uint8
|
||||
SeqNum uint32
|
||||
Length uint32
|
||||
}
|
||||
|
||||
// Session 会话状态
|
||||
type Session struct {
|
||||
SessionID uint64
|
||||
ClientAddr *net.UDPAddr
|
||||
ServerAddr *net.UDPAddr
|
||||
SendSeq uint32
|
||||
RecvSeq uint32
|
||||
AESKey []byte
|
||||
LastActive time.Time
|
||||
RTT time.Duration
|
||||
LossRate float64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// RelayServer 转发服务器
|
||||
type RelayServer struct {
|
||||
listenAddr *net.UDPAddr
|
||||
conn *net.UDPConn
|
||||
sessions sync.Map // map[uint64]*Session
|
||||
aesGCM cipher.AEAD
|
||||
}
|
||||
|
||||
// Start 启动转发服务
|
||||
func (s *RelayServer) Start() error {
|
||||
conn, err := net.ListenUDP("udp4", s.listenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.conn = conn
|
||||
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, clientAddr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go s.handlePacket(buf[:n], clientAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePacket 处理收到的包
|
||||
func (s *RelayServer) handlePacket(data []byte, clientAddr *net.UDPAddr) {
|
||||
// 解析报文头
|
||||
if len(data) < HeaderSize {
|
||||
return
|
||||
}
|
||||
|
||||
header := parseHeader(data[:HeaderSize])
|
||||
if header.Magic != Magic {
|
||||
return
|
||||
}
|
||||
|
||||
switch header.Type {
|
||||
case TypeDATA:
|
||||
s.handleData(header, data[HeaderSize:], clientAddr)
|
||||
case TypeHEART:
|
||||
s.handleHeartbeat(header, clientAddr)
|
||||
case TypeACK:
|
||||
s.handleACK(header, clientAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// handleData 处理数据包
|
||||
func (s *RelayServer) handleData(header RelayHeader, payload []byte, clientAddr *net.UDPAddr) {
|
||||
// 获取或创建会话
|
||||
session := s.getOrCreateSession(clientAddr)
|
||||
|
||||
// 解密载荷
|
||||
plaintext, err := s.decrypt(payload, session.AESKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新序列号
|
||||
session.mu.Lock()
|
||||
session.RecvSeq = header.SeqNum
|
||||
session.LastActive = time.Now()
|
||||
session.mu.Unlock()
|
||||
|
||||
// 转发到游戏服务器
|
||||
_, err = s.conn.WriteToUDP(plaintext, session.ServerAddr)
|
||||
if err != nil {
|
||||
// 记录错误,触发重连
|
||||
s.handleForwardError(session, err)
|
||||
}
|
||||
|
||||
// 发送ACK(如果需要可靠传输)
|
||||
if header.Flags&0x01 != 0 {
|
||||
s.sendACK(session, header.SeqNum)
|
||||
}
|
||||
}
|
||||
|
||||
// handleHeartbeat 处理心跳
|
||||
func (s *RelayServer) handleHeartbeat(header RelayHeader, clientAddr *net.UDPAddr) {
|
||||
session := s.getSession(clientAddr)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
session.LastActive = time.Now()
|
||||
// 计算RTT(心跳包中有时间戳)
|
||||
session.mu.Unlock()
|
||||
|
||||
// 回复心跳
|
||||
s.sendHeartbeatReply(session)
|
||||
}
|
||||
|
||||
// ForwardLoop 转发循环(从游戏服务器到客户端)
|
||||
func (s *RelayServer) ForwardLoop(session *Session) {
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, err := session.ServerConn.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// 加密
|
||||
ciphertext := s.encrypt(buf[:n], session.AESKey)
|
||||
|
||||
// 封装
|
||||
header := RelayHeader{
|
||||
Magic: Magic,
|
||||
Version: Version,
|
||||
Type: TypeDATA,
|
||||
SeqNum: session.SendSeq,
|
||||
Length: uint32(len(ciphertext)),
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
session.SendSeq++
|
||||
session.mu.Unlock()
|
||||
|
||||
// 发送
|
||||
packet := append(header.Bytes(), ciphertext...)
|
||||
s.conn.WriteToUDP(packet, session.ClientAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt AES-256-GCM加密
|
||||
func (s *RelayServer) encrypt(plaintext []byte, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
gcm, _ := cipher.NewGCM(block)
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
// 生成随机nonce
|
||||
return gcm.Seal(nil, nonce, plaintext, nil)
|
||||
}
|
||||
|
||||
// CheckSessionTimeout 会话超时检查
|
||||
func (s *RelayServer) CheckSessionTimeout() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
for range ticker.C {
|
||||
s.sessions.Range(func(key, value interface{}) bool {
|
||||
session := value.(*Session)
|
||||
if time.Since(session.LastActive) > 60*time.Second {
|
||||
// 会话超时,清理
|
||||
s.sessions.Delete(key)
|
||||
session.Close()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4.2 商业版阶段 — QUIC Tunnel
|
||||
|
||||
### 4.2.1 为什么选择QUIC
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ QUIC vs UDP Relay vs TCP │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 特性 │ UDP Relay │ QUIC │ TCP │
|
||||
│ ──────────────┼───────────┼────────────┼──────────── │
|
||||
│ 连接建立延迟 │ 0-RTT │ 0-RTT │ 3-RTT (TLS) │
|
||||
│ 多路复用 │ ✗ │ ✓ │ ✗ (HOL阻塞) │
|
||||
│ 可靠传输 │ 可选 │ 可选 │ 必须 │
|
||||
│ 流量控制 │ 手动 │ 内置 │ 内置 │
|
||||
│ 拥塞控制 │ 手动 │ BBR/Cubic │ Cubic │
|
||||
│ 连接迁移 │ 手动 │ 内置 │ ✗ │
|
||||
│ 加密 │ 手动 │ 内置(TLS1.3)│ 需要TLS │
|
||||
│ 前向纠错 │ 手动 │ 可扩展 │ ✗ │
|
||||
│ 多路径 │ 手动 │ 扩展中 │ MPTCP │
|
||||
│ ──────────────┼───────────┼────────────┼──────────── │
|
||||
│ 适合场景 │ 简单转发 │ 游戏加速 │ 文件传输 │
|
||||
│ │
|
||||
│ 结论:QUIC是最优选择 │
|
||||
│ - 0-RTT连接建立(快速切换节点) │
|
||||
│ - 多路复用无HOL阻塞(多游戏流量并行) │
|
||||
│ - 内置加密(安全性) │
|
||||
│ - 连接迁移(IP变化不断线) │
|
||||
│ - 可扩展(加FEC、多路径) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.2 QUIC Tunnel协议设计
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ QUIC Tunnel 架构 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ 应用层 │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ Stream 0│ │ Stream 1│ │ Stream 2│ │ Stream N│ │ │
|
||||
│ │ │ 游戏数据│ │ 控制流 │ │ 心跳流 │ │ 其他 │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ QUIC层 │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ QUIC Frames: │ │ │
|
||||
│ │ │ - STREAM (数据帧) │ │ │
|
||||
│ │ │ - ACK (确认帧) │ │ │
|
||||
│ │ │ - CRYPTO (加密握手) │ │ │
|
||||
│ │ │ - PADDING (填充) │ │ │
|
||||
│ │ │ - CONNECTION_CLOSE (关闭) │ │ │
|
||||
│ │ │ - FEC (前向纠错,自定义扩展) │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ TLS 1.3层 │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ - 握手加密 │ │ │
|
||||
│ │ │ - 会话密钥轮换 │ │ │
|
||||
│ │ │ - 证书验证 │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ UDP层 │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ - UDP Socket │ │ │
|
||||
│ │ │ - BBR拥塞控制 │ │ │
|
||||
│ │ │ - 多路径支持 │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.3 Multipath多路径传输
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Multipath 多路径设计 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────┐ ┌────────┐ │
|
||||
│ │ │──── Path 1 (联通) ───→ │ │
|
||||
│ │ 客户端 │──── Path 2 (电信) ───→ 接入节点│──→ 游戏服务器 │
|
||||
│ │ │──── Path 3 (移动) ───→ │ │
|
||||
│ └────────┘ └────────┘ │
|
||||
│ │
|
||||
│ 路径管理: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ PathTable: │ │
|
||||
│ │ ┌─────────┬────────┬────────┬────────┬────────┐ │ │
|
||||
│ │ │ PathID │ RTT │ Loss │ Bw │ Weight │ │ │
|
||||
│ │ ├─────────┼────────┼────────┼────────┼────────┤ │ │
|
||||
│ │ │ path-1 │ 30ms │ 0.1% │ 50Mbps │ 0.5 │ │ │
|
||||
│ │ │ path-2 │ 45ms │ 0.5% │ 30Mbps │ 0.3 │ │ │
|
||||
│ │ │ path-3 │ 60ms │ 1.0% │ 20Mbps │ 0.2 │ │ │
|
||||
│ │ └─────────┴────────┴────────┴────────┴────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 数据分配策略: │
|
||||
│ 1. 关键数据(游戏动作)→ 走最优路径(path-1) │
|
||||
│ 2. 状态数据(位置同步)→ 分散到多路径 │
|
||||
│ 3. 冗余数据(FEC)→ 走次优路径 │
|
||||
│ 4. 实时监控路径质量,动态调整权重 │
|
||||
│ │
|
||||
│ 路径切换: │
|
||||
│ - 主路径故障 → 无缝切换到备用路径 │
|
||||
│ - 切换延迟 < 100ms │
|
||||
│ - 连接保持(Session ID不变) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.4 FEC前向纠错
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ FEC前向纠错设计 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 原理:发送N个数据包 + K个冗余包,丢任意K个包都能恢复 │
|
||||
│ │
|
||||
│ 编码方式:Reed-Solomon码 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 示例:RS(10,4) — 10个数据包 + 4个冗余包 │ │
|
||||
│ │ │ │
|
||||
│ │ 发送端: │ │
|
||||
│ │ D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 ← 数据包 │ │
|
||||
│ │ P1 P2 P3 P4 ← 冗余包(校验包) │ │
|
||||
│ │ ───────────────────────────────── │ │
|
||||
│ │ 共14个包,允许丢4个包 │ │
|
||||
│ │ │ │
|
||||
│ │ 接收端(假设D2,D5,D8,P2丢失): │ │
|
||||
│ │ D1 ✗ D3 D4 ✗ D6 D7 ✗ D9 D10 │ │
|
||||
│ │ ✗ P3 P4 │ │
|
||||
│ │ ───────────────────────────────── │ │
|
||||
│ │ 通过RS解码恢复D2,D5,D8,P2 │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 动态FEC参数调整: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 丢包率 │ FEC比例 │ 说明 │ │
|
||||
│ ├─────────────┼────────────┼──────────────────────────────│ │
|
||||
│ │ < 1% │ N=10, K=1 │ 低冗余,节省带宽 │ │
|
||||
│ │ 1% - 5% │ N=10, K=2 │ 中等冗余 │ │
|
||||
│ │ 5% - 10% │ N=10, K=3 │ 高冗余 │ │
|
||||
│ │ > 10% │ N=10, K=4 │ 最大冗余,保护关键数据 │ │
|
||||
│ │ > 20% │ 切换路径 │ 丢包太严重,切换链路 │ │
|
||||
│ └─────────────┴────────────┴──────────────────────────────┘ │
|
||||
│ │
|
||||
│ 实现代码(Go): │
|
||||
│ │
|
||||
│ type FECEncoder struct { │
|
||||
│ dataShards int // 数据分片数 │
|
||||
│ parityShards int // 冗余分片数 │
|
||||
│ enc reedsolomon.Encoder │
|
||||
│ } │
|
||||
│ │
|
||||
│ func (f *FECEncoder) Encode(data []byte) [][]byte { │
|
||||
│ // 将数据分割为dataShards个分片 │
|
||||
│ shards := f.splitData(data, f.dataShards) │
|
||||
│ // 生成parityShards个冗余分片 │
|
||||
│ err := f.enc.Encode(shards) │
|
||||
│ // 返回所有分片(数据+冗余) │
|
||||
│ return shards │
|
||||
│ } │
|
||||
│ │
|
||||
│ func (f *FECEncoder) Decode(shards [][]byte) ([]byte, error) { │
|
||||
│ // 通过RS解码恢复丢失的分片 │
|
||||
│ err := f.enc.Reconstruct(shards) │
|
||||
│ // 合并分片为原始数据 │
|
||||
│ return f.mergeShards(shards[:f.dataShards]), err │
|
||||
│ } │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.5 动态拥塞控制
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 动态拥塞控制设计 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 目标:在不丢包的前提下,最大化利用带宽 │
|
||||
│ │
|
||||
│ 算法选择:BBR (Bottleneck Bandwidth and Round-trip) │
|
||||
│ │
|
||||
│ BBR工作原理: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 1. 测量瓶颈带宽 (BtlBw) │ │
|
||||
│ │ - 跟踪最大交付速率 │ │
|
||||
│ │ - 滑动窗口取最大值 │ │
|
||||
│ │ │ │
|
||||
│ │ 2. 测量最小RTT (RTprop) │ │
|
||||
│ │ - 跟踪最小往返时间 │ │
|
||||
│ │ - 10秒窗口取最小值 │ │
|
||||
│ │ │ │
|
||||
│ │ 3. 计算发送速率 │ │
|
||||
│ │ cwnd = BtlBw × RTprop × gain │ │
|
||||
│ │ pacing_rate = BtlBw × gain │ │
|
||||
│ │ │ │
|
||||
│ │ 4. 四个阶段循环: │ │
|
||||
│ │ Startup → Drain → ProbeBW → ProbeRTT │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 游戏场景优化: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. 低延迟优先 │ │
|
||||
│ │ - 限制最大cwnd,避免缓冲区膨胀 │ │
|
||||
│ │ - pacing_rate不超过实际带宽的80% │ │
|
||||
│ │ │ │
|
||||
│ │ 2. 快速恢复 │ │
|
||||
│ │ - 丢包时快速降低发送速率 │ │
|
||||
│ │ - 恢复时快速提升(比标准BBR更快) │ │
|
||||
│ │ │ │
|
||||
│ │ 3. 多流公平 │ │
|
||||
│ │ - 多个游戏流共享带宽时公平分配 │ │
|
||||
│ │ - 优先级流获得更多带宽 │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.6 UDP重传优化
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ UDP重传优化设计 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 问题:标准UDP不重传,丢包就丢了 │
|
||||
│ 方案:选择性重传 + 优先级重传 │
|
||||
│ │
|
||||
│ 数据分类: │
|
||||
│ ┌──────────┬────────────────┬────────────┬──────────────┐ │
|
||||
│ │ 优先级 │ 数据类型 │ 重传策略 │ 超时时间 │ │
|
||||
│ ├──────────┼────────────────┼────────────┼──────────────┤ │
|
||||
│ │ P0 │ 玩家操作指令 │ 必须重传 │ 50ms │ │
|
||||
│ │ P1 │ 游戏状态同步 │ 选择重传 │ 100ms │ │
|
||||
│ │ P2 │ 位置更新 │ 不重传 │ - │ │
|
||||
│ │ P3 │ 视觉效果数据 │ 不重传 │ - │ │
|
||||
│ └──────────┴────────────────┴────────────┴──────────────┘ │
|
||||
│ │
|
||||
│ 重传流程: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ 发送端: │ │
|
||||
│ │ 1. 发送数据包,记录发送时间 │ │
|
||||
│ │ 2. 等待ACK │ │
|
||||
│ │ 3. 超时未收到ACK → 检查优先级 │ │
|
||||
│ │ 4. P0/P1 → 立即重传 │ │
|
||||
│ │ 5. P2/P3 → 放弃,等新数据 │ │
|
||||
│ │ │ │
|
||||
│ │ 接收端: │ │
|
||||
│ │ 1. 收到数据包 → 发送ACK │ │
|
||||
│ │ 2. 检测到序列号跳跃 → 发送NACK(选择性否定确认) │ │
|
||||
│ │ 3. 乱序到达 → 缓存并重排序(10ms窗口) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 伪代码: │
|
||||
│ │
|
||||
│ type RetransmitQueue struct { │
|
||||
│ pending map[uint32]*Packet │
|
||||
│ mu sync.Mutex │
|
||||
│ } │
|
||||
│ │
|
||||
│ func (q *RetransmitQueue) Add(pkt *Packet) { │
|
||||
│ q.mu.Lock() │
|
||||
│ defer q.mu.Unlock() │
|
||||
│ pkt.SendTime = time.Now() │
|
||||
│ q.pending[pkt.SeqNum] = pkt │
|
||||
│ } │
|
||||
│ │
|
||||
│ func (q *RetransmitQueue) CheckTimeout() []*Packet { │
|
||||
│ q.mu.Lock() │
|
||||
│ defer q.mu.Unlock() │
|
||||
│ var retransmit []*Packet │
|
||||
│ now := time.Now() │
|
||||
│ for seq, pkt := range q.pending { │
|
||||
│ if now.Sub(pkt.SendTime) > pkt.Timeout { │
|
||||
│ if pkt.Priority <= P1 { │
|
||||
│ retransmit = append(retransmit, pkt) │
|
||||
│ pkt.SendTime = now │
|
||||
│ pkt.RetransmitCount++ │
|
||||
│ } else { │
|
||||
│ delete(q.pending, seq) │
|
||||
│ } │
|
||||
│ } │
|
||||
│ } │
|
||||
│ return retransmit │
|
||||
│ } │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2.7 智能链路切换
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 智能链路切换设计 │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 切换触发条件: │
|
||||
│ 1. 当前链路延迟 > 最优链路延迟 × 1.5 │
|
||||
│ 2. 当前链路丢包率 > 5% │
|
||||
│ 3. 连续3次探测质量下降 │
|
||||
│ 4. 当前链路断开 │
|
||||
│ │
|
||||
│ 切换流程: │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ 探测新 │───→│ 建立新 │───→│ 迁移会话 │ │ │
|
||||
│ │ │ 链路质量 │ │ 连接 │ │ 状态 │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌─────────────────────┘ │ │
|
||||
│ │ │ ↓ │ │
|
||||
│ │ │ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ │ 切换流量 │───→│ 关闭旧 │ │ │
|
||||
│ │ │ │ 到新链路 │ │ 连接 │ │ │
|
||||
│ │ │ └──────────┘ └──────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ └─── 整个过程 < 200ms,用户无感知 │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 会话保持: │
|
||||
│ - 使用SessionID标识会话 │
|
||||
│ - 切换时携带SessionID到新链路 │
|
||||
│ - 新链路通过SessionID恢复会话状态 │
|
||||
│ - 序列号连续(不因切换而重置) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user