3.1 游戏网络基础
游戏网络与Web网络有本质区别。Web可以容忍秒级延迟,游戏要求毫秒级;Web可以使用大块数据传输,游戏需要处理海量小包高频传输。
游戏网络的特点
特点1:小包高频
问题:游戏网络的数据包有什么特点?
// 游戏网络包特点分析
type GamePacket struct {
Size int // 包大小
Rate int // 发送频率
}
// 典型游戏包对比
var typicalPackets = []GamePacket{
// MOBA游戏:每秒20-30次位置更新,每次50-100字节
{Size: 80, Rate: 30}, // 2.4 KB/秒
// FPS游戏:每秒60次输入+位置,每次30-50字节
{Size: 40, Rate: 60}, // 2.4 KB/秒
// MMORPG:每秒5-10次状态同步,每次200-500字节
{Size: 300, Rate: 10}, // 3 KB/秒
// Web请求:一次请求1-10 KB,频率低
{Size: 5000, Rate: 0.2}, // 1 KB/秒
}
// 游戏网络的挑战:
// 1. TCP头部开销大(20字节IP头 + 20字节TCP头 = 40字节)
// 2. 小包比例高:80字节的数据包,40字节是头部,开销50%!
// 3. 高频发送:每秒30个包 = 每秒1200字节头部开销
解决方案:
- 使用UDP减少头部开销(8字节UDP头 vs 20字节TCP头)
- 批量发送:合并多个小包
- 压缩数据:减少payload大小
特点2:低延迟要求
问题:不同游戏类型能容忍多少延迟?
// LatencyTolerance 延迟容忍度
type LatencyTolerance struct {
GameType string
MaxLatency time.Duration
Target time.Duration
}
var toleranceTable = []LatencyTolerance{
// 回合制:延迟无影响
{GameType: "回合制", MaxLatency: 5 * time.Second, Target: 1 * time.Second},
// 卡牌游戏:500ms以内可接受
{GameType: "卡牌", MaxLatency: 500 * time.Millisecond, Target: 200 * time.Millisecond},
// MMORPG:300ms以内可接受
{GameType: "MMORPG", MaxLatency: 300 * time.Millisecond, Target: 100 * time.Millisecond},
// MOBA:100ms以内可接受
{GameType: "MOBA", MaxLatency: 100 * time.Millisecond, Target: 50 * time.Millisecond},
// FPS:50ms以内可接受
{GameType: "FPS", MaxLatency: 50 * time.Millisecond, Target: 20 * time.Millisecond},
// 格斗游戏:20ms以内可接受
{GameType: "格斗", MaxLatency: 20 * time.Millisecond, Target: 10 * time.Millisecond},
}
关键指标:
| 指标 | 定义 | 影响 | 优化方法 |
|---|---|---|---|
| 延迟(Latency) | 数据包往返时间 | 操作响应速度 | 选择低延迟协议、优化服务器部署 |
| 抖动(Jitter) | 延迟的变化幅度 | 画面卡顿 | 客户端插值、缓冲区平滑 |
| 丢包率(Packet Loss) | 丢失数据包的比例 | 操作失败 | 重传机制、前向纠错 |
特点3:容忍丢包但不容忍乱序
问题:游戏数据包可以丢,但不能乱序?
// 游戏数据包分类
type PacketCategory int
const (
CriticalPacket PacketCategory = iota // 关键包:不能丢,不能乱序
ImportantPacket // 重要包:不能丢,可以乱序
TrivialPacket // 普通包:可以丢,可以乱序
)
// 不同类型包的处理
type PacketHandler struct {
criticalQueue chan *Packet // 关键包:可靠有序
importantQueue chan *Packet // 重要包:可靠无序
trivialQueue chan *Packet // 普通包:不可靠无序
}
// 处理数据包
func (ph *PacketHandler) HandlePacket(pkt *Packet, category PacketCategory) {
switch category {
case CriticalPacket:
// 例如:玩家登录、购买物品
// 需要:ACK确认 + 序列号保证顺序
ph.sendWithAck(pkt)
ph.waitForAck(pkt)
case ImportantPacket:
// 例如:聊天消息、好友请求
// 需要:ACK确认,但不保证顺序
ph.sendWithAck(pkt)
case TrivialPacket:
// 例如:玩家位置更新(每秒30次,丢一两次无所谓)
// 不需要:ACK,不保证顺序
ph.sendFireAndForget(pkt)
}
}
技术方案:
- 关键包:TCP或带ACK的UDP + 序列号
- 重要包:带ACK的UDP,无需序列号
- 普通包:UDP,不处理丢失和乱序
网络性能监控
实时监控网络质量
// NetworkMonitor 网络质量监控
type NetworkMonitor struct {
// 统计数据
pingHistory []time.Duration // 最近100次ping
jitterHistory []time.Duration // 最近100次抖动
packetLossRate float64 // 丢包率
// 计算辅助
lastPingTime time.Time
pingCount int
lostPacketCount int
}
// UpdatePing 更新ping数据
func (nm *NetworkMonitor) UpdatePing(ping time.Time) {
now := time.Now()
latency := now.Sub(ping)
// 更新历史记录(保留最近100次)
nm.pingHistory = append(nm.pingHistory, latency)
if len(nm.pingHistory) > 100 {
nm.pingHistory = nm.pingHistory[1:]
}
// 计算抖动(延迟变化)
if len(nm.pingHistory) >= 2 {
lastLatency := nm.pingHistory[len(nm.pingHistory)-2]
jitter := latency - lastLatency
if jitter < 0 {
jitter = -jitter
}
nm.jitterHistory = append(nm.jitterHistory, jitter)
if len(nm.jitterHistory) > 100 {
nm.jitterHistory = nm.jitterHistory[1:]
}
}
nm.pingCount++
nm.lastPingTime = now
}
// 计算平均延迟
func (nm *NetworkMonitor) GetAverageLatency() time.Duration {
if len(nm.pingHistory) == 0 {
return 0
}
var sum time.Duration
for _, latency := range nm.pingHistory {
sum += latency
}
return sum / time.Duration(len(nm.pingHistory))
}
// 计算平均抖动
func (nm *NetworkMonitor) GetAverageJitter() time.Duration {
if len(nm.jitterHistory) == 0 {
return 0
}
var sum time.Duration
for _, jitter := range nm.jitterHistory {
sum += jitter
}
return sum / time.Duration(len(nm.jitterHistory))
}
// 计算丢包率
func (nm *NetworkMonitor) GetPacketLossRate() float64 {
if nm.pingCount == 0 {
return 0
}
return float64(nm.lostPacketCount) / float64(nm.pingCount)
}
// 网络质量评分(0-100)
func (nm *NetworkMonitor) GetQualityScore() float64 {
latency := nm.GetAverageLatency()
jitter := nm.GetAverageJitter()
loss := nm.GetPacketLossRate()
// 简化评分算法
score := 100.0
// 延迟惩罚:每100ms扣20分
score -= latency.Seconds() * 1000 / 100 * 20
// 抖动惩罚:每50ms扣10分
score -= jitter.Seconds() * 1000 / 50 * 10
// 丢包惩罚:每1%扣5分
score -= loss * 100 * 5
if score < 0 {
score = 0
}
return score
}
真实案例:《王者荣耀》网络优化
背景:
- 目标玩家:移动网络为主
- 延迟要求:<100ms
- 网络环境:3G/4G/WiFi切换频繁
技术挑战:
- 移动网络延迟波动大(50-300ms)
- 丢包率高(1-10%)
- 网络切换(3G→4G→WiFi)导致连接断开
解决方案:
1. 协议优化:TCP → KCP
// TCP vs KCP 对比
type ProtocolComparison struct {
Protocol string
AvgLatency time.Duration
Jitter time.Duration
PacketLoss float64
}
// 优化前(TCP)
var tcpStats = ProtocolComparison{
Protocol: "TCP",
AvgLatency: 150 * time.Millisecond,
Jitter: 50 * time.Millisecond,
PacketLoss: 2.0, // %
}
// 优化后(KCP)
var kcpStats = ProtocolComparison{
Protocol: "KCP",
AvgLatency: 80 * time.Millisecond, // 降低47%
Jitter: 20 * time.Millisecond, // 降低60%
PacketLoss: 0.5, // 降低75%
}
2. 弱网优化:断线重连 + 状态缓存
// WeakNetworkOptimizer 弱网优化器
type WeakNetworkOptimizer struct {
// 断线检测
lastReceiveTime time.Time
timeout time.Duration
// 状态缓存
stateCache *GameStateCache
}
// 检测网络断开
func (wno *WeakNetworkOptimizer) CheckConnection() bool {
if time.Since(wno.lastReceiveTime) > wno.timeout {
// 网络可能断开
return false
}
return true
}
// 重连后恢复状态
func (wno *WeakNetworkOptimizer) Reconnect() error {
// 1. 重新连接服务器
if err := wno.connect(); err != nil {
return err
}
// 2. 请求状态同步
state, err := wno.requestStateSync()
if err != nil {
return err
}
// 3. 恢复游戏状态
wno.restoreState(state)
return nil
}
3. 网络质量自适应
// NetworkQualityAdaptor 网络质量自适应
type NetworkQualityAdaptor struct {
// 根据网络质量调整策略
sendRate int // 发送频率
packetSize int // 包大小
}
func (nqa *NetworkQualityAdaptor) Adapt(quality float64) {
if quality > 80 {
// 网络良好:高频率发送
nqa.sendRate = 30 // 30次/秒
nqa.packetSize = 100 // 100字节/包
} else if quality > 50 {
// 网络一般:降低频率
nqa.sendRate = 20 // 20次/秒
nqa.packetSize = 150 // 增大包大小
} else {
// 网络差:最低频率
nqa.sendRate = 10 // 10次/秒
nqa.packetSize = 200 // 继续增大包大小
}
}
效果对比:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 平均延迟 | 150ms | 80ms | 47% ↓ |
| 丢包率 | 2% | 0.5% | 75% ↓ |
| 断线重连成功率 | 65% | 92% | 41% ↑ |
| 玩家满意度 | 68分 | 85分 | 25% ↑ |
踩坑经验
❌ 错误1:不考虑网络环境,所有玩家使用相同策略
// 问题:4G玩家和WiFi玩家使用相同的发送频率
func (s *Server) SendPosition(player *Player, pos Position) {
s.Send(player, pos) // 每秒30次
}
// 正确:根据网络质量调整
func (s *Server) SendPosition(player *Player, pos Position) {
quality := player.NetworkQuality
if quality > 80 {
// 网络好:每秒30次
s.Send(player, pos)
} else if quality > 50 {
// 网络一般:每秒20次
if time.Since(player.LastSendTime) > 50*time.Millisecond {
s.Send(player, pos)
}
} else {
// 网络差:每秒10次
if time.Since(player.LastSendTime) > 100*time.Millisecond {
s.Send(player, pos)
}
}
}
❌ 错误2:不监控网络质量
// 问题:不知道玩家网络状况,无法优化
func (c *Client) SendPacket(pkt *Packet) {
c.conn.Write(pkt.Data) // 直接发送
}
// 正确:监控网络质量,自适应调整
func (c *Client) SendPacket(pkt *Packet) {
// 1. 检查网络质量
quality := c.monitor.GetQualityScore()
// 2. 根据质量调整策略
if quality < 50 {
// 网络差,减少发送频率
if time.Since(c.LastSendTime) < 100*time.Millisecond {
return // 跳过本次发送
}
}
// 3. 发送数据包
c.conn.Write(pkt.Data)
c.LastSendTime = time.Now()
// 4. 记录发送时间,用于计算RTT
c.monitor.RecordSend(pkt.ID, time.Now())
}
小结
游戏网络基础的核心要点:
- 游戏网络特点:小包高频、低延迟、容忍丢包
- 关键指标:延迟、抖动、丢包率
- 网络监控:实时监控网络质量
- 弱网优化:协议优化、断线重连、自适应调整
真实案例:
- 《王者荣耀》:TCP → KCP,延迟降低47%
- 《和平精英》:弱网优化,断线重连成功率提升41%
踩坑经验:
- ❌ 不要忽略网络质量监控
- ❌ 不要对所有玩家使用相同策略
- ❌ 不要在移动网络使用TCP
下一节(3.2)我们将学习:I/O模型与事件通知机制,深入了解select/poll/epoll/IOCP。