Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)
    }
}

技术方案

  1. 关键包:TCP或带ACK的UDP + 序列号
  2. 重要包:带ACK的UDP,无需序列号
  3. 普通包: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切换频繁

技术挑战

  1. 移动网络延迟波动大(50-300ms)
  2. 丢包率高(1-10%)
  3. 网络切换(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  // 继续增大包大小
    }
}

效果对比

指标优化前优化后提升
平均延迟150ms80ms47% ↓
丢包率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())
}

小结

游戏网络基础的核心要点:

  1. 游戏网络特点:小包高频、低延迟、容忍丢包
  2. 关键指标:延迟、抖动、丢包率
  3. 网络监控:实时监控网络质量
  4. 弱网优化:协议优化、断线重连、自适应调整

真实案例

  • 《王者荣耀》:TCP → KCP,延迟降低47%
  • 《和平精英》:弱网优化,断线重连成功率提升41%

踩坑经验

  • ❌ 不要忽略网络质量监控
  • ❌ 不要对所有玩家使用相同策略
  • ❌ 不要在移动网络使用TCP

下一节(3.2)我们将学习:I/O模型与事件通知机制,深入了解select/poll/epoll/IOCP。