如何在香港服务器的 Windows Server 2019 上部署网络游戏:如何把跨境 UDP 掉包率从 6.7% 打到 0.3%

我盯着香港将军澳机房的业务监控大屏上的延迟曲线发呆:晚高峰 21:00—23:30,来自华南的玩家 UDP 掉包率飙到了 6.7%,语音在抖、技能放不出去,社区里已经有人发“卡到想退坑”。运营同学在电话那头问我:“今晚能压下去吗?”
我说试试。下面是我这次**把跨境 UDP 掉包率从 6.7% 稳到 0.3%**的完整过程与可复用的操作清单。写给此刻也在机房里跟网络较劲的你。
现场环境与目标
业务形态
- 自研实时对战游戏,主通道 UDP(游戏帧同步、语音),少量 TCP(登陆、匹配、资产)。
- 服务端固定端口段:UDP/27015-27030, 30000-30100,语音旁路 UDP/50000-50050。
机房与链路
- 位置:香港将军澳(HK/TKO)。
- 上联:双线接入(PCCW + CMI),进 HKIX,内地回程各自走国际回程(未做专线回国)。
- ToR:Arista 7xxx,单服务器 10G 接入,双上联 ECMP。
- BGP:机房侧统一出网;服务器不跑 BGP。
服务器硬件(其中一台“Game-07”)
- 机型:1U 定制
- CPU:Intel Xeon E-2288G(8C/16T,睿频 5.0GHz)
- 内存:64GB DDR4 ECC
- 系统盘:Intel P4510 2TB NVMe
- 网卡:Intel X710-DA2(双口 10G SFP+,驱动 1.11.208.0)
- 操作系统:Windows Server 2019 Datacenter (1809) 17763.5292
优化目标(SLA)
峰值时段(21:00—23:30)内地跨境用户:
- 平均掉包率 ≤ 0.5%
- P95 抖动 ≤ 10 ms
- 长会话(≥30 分钟)中断率 ≤ 0.8%
诊断:先用数据定位“瓶颈在路还是在端”
我从“三板斧”开始:端口、路径、速率。
端口打通 & 丢包基线(iperf3 UDP)
在香港服务端开 iperf3 -s -p 30001 -i 1
大陆多点(广州、深圳、长沙的云主机)跑 UDP 模式:
iperf3 -c <HK_IP> -p 30001 -u -b 20M -l 1200 -t 120 -i 1
结果:广州/深圳在 20 Mbps 下抖动 OK,丢 5%~8%;长沙丢 10%+。说明跨境路径本身就不稳,但服务端还有优化空间(CPU、队列、驱动)。
机房到边界的路径(pathping / tracert)
pathping -q 50 -h 20 <client_ip>
看到丢包主要出现在跨境段(CMI 回程),但本地 hop 无异常。
服务端网络栈与网卡队列(PerfMon + PowerShell)
关键计数器:
- \UDPv4\Datagrams Received Errors
- \UDPv4\Datagrams No Ports
- \Network Interface(*)\Packets Received Discarded
- \Processor(_Total)\DPC Rate
峰值期 Packets Received Discarded 有抖高,说明网卡/内核接收队列存在压力。
应用层观测
房间服统计显示:20:40 起 server_recv_queue_drops 抬头,说明服务端应用没及时取包(CPU 竞争或队列太浅)。
结论:跨境路径不可控因素 + 服务器端队列/调度可控因素共同导致丢包。先把可控的做到极致,再考虑跨境线路策略。
优化路线图(四层 12 步)
L1 物理/驱动层:把包稳稳接住
1) 固件与电源
BIOS 设为 Performance,关深度 C-states(避免 DPC 抖动)。
Windows 电源计划切为 高性能:
- powercfg /setactive SCHEME_MIN
2) 网卡驱动与队列
X710 升级到与固件匹配的稳定版(注意和 ToR 兼容列表)。
检查并开启 RSS(多核收包):
Get-NetAdapterRss -Name "Ethernet 2"
Enable-NetAdapterRss -Name "Ethernet 2"
# 限定核与队列,避免跨 NUMA(单路 E-2288G 可不设 NUMA)
Set-NetAdapterRss -Name "Ethernet 2" -BaseProcessorNumber 2 -MaxProcessors 6
调整接收/发送缓冲和中断调制(不同驱动显示名略有差异):
# 尝试把 Receive Buffers 调到 2048 或 4096,Transmit Buffers 调到 1024+
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "Receive Buffers" -DisplayValue "4096"
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "Transmit Buffers" -DisplayValue "2048"
# 中断调制率:先用 Adaptive,再根据 DPC 观察微调
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "Interrupt Moderation Rate" -DisplayValue "Adaptive"
校验和卸载(减 CPU)——若驱动提供该项,保持开启:
# 通用写法:按显示名启用(不同厂商项名不同)
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "UDP Checksum Offload (IPv4)" -DisplayValue "Enabled" -ErrorAction SilentlyContinue
Set-NetAdapterAdvancedProperty -Name "Ethernet 2" -DisplayName "UDP Checksum Offload (IPv6)" -DisplayValue "Enabled" -ErrorAction SilentlyContinue
经验:X710 在 Receive Buffers=4096、Interrupt Moderation=Adaptive 下,Packets Discarded 明显下降;过高的中断合并会增大抖动,过低会吃 CPU,要结合 DPC Rate 找平衡。
3) MTU 与碎片
互联网侧保持 MTU 1500,不要随意开 Jumbo。若你经过 GRE/IPSec,按隧道开销把 MTU 调到 1472/1460:
netsh interface ipv4 show subinterfaces
netsh interface ipv4 set subinterface "Ethernet 2" mtu=1500 store=persistent
应用侧把 UDP 负载控制在 ≤1200B(头部+潜在封装留余量),杜绝 IP 碎片。
L2 Windows 网络栈:让属于游戏的包优先被处理
4) 开放端口 & 旁路不必要检查
$ports = @("27015-27030","30000-30100","50000-50050")
foreach($p in $ports){
netsh advfirewall firewall add rule name="Game_UDP_$p" dir=in action=allow protocol=UDP localport=$p
netsh advfirewall firewall add rule name="Game_UDP_$p_out" dir=out action=allow protocol=UDP localport=$p
}
说明:显式允许可降低复杂匹配、减少 CPU 抖动(尤其规则很多时)。
5) Policy-based QoS(打 DSCP)
跨境运营商大多不保留 DSCP,但本机 → ToR → 出口链路常常会按 DSCP 排队,对拥塞时序有帮助。
# 将游戏 UDP 端口打 DSCP=46(EF)
New-NetQosPolicy -Name "GameUDP" `
-IPProtocolMatchCondition UDP `
-DestinationPortRange 27015-27030,30000-30100,50000-50050 `
-DSCPAction 46
# 可选:限制后台更新/日志出网速率,给游戏让路
New-NetQosPolicy -Name "BG_Limit" -AppPathNameMatchCondition "C:\Agent\*.exe" -ThrottleRateActionBitsPerSecond 2000000
6) 端口与资源耗尽防护
高并发场景下,偶发出现源端口耗尽/短暂不可用,统一把系统上限拉满:
Windows Registry Editor Version=5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
"MaxUserPort"=dword:0000fffe ; 65534
"UdpEphemeralInterval"=dword:0000001e ; (可选) 30s,视业务而定
注:大多数游戏服务端“被动接收”为主,MaxUserPort影响有限,但我仍统一做成基线。
7) 观测即调优:内核抓丢包原因(PktMon)
pktmon filter remove
pktmon filter add -t UDP -p 27015-27030
pktmon start --etw -c
# 峰值过后
pktmon stop
pktmon format PktMon.etl -o udp_trace.pcapng
# 用 Wireshark 分析是否有校验错误/重复/乱序
L3 应用/协议层:把“丢包不可避免”变成“丢也不怕”
8) 套接字最佳实践(C++ 伪代码)
禁用 UDP “端口不可达”对 Recv 的干扰(Windows 独有,强烈推荐):
// 禁掉 SIO_UDP_CONNRESET,避免对端关闭导致 WSAECONNRESET 影响收包
BOOL newBehavior = FALSE;
DWORD retBytes = 0;
WSAIoctl(sock, SIO_UDP_CONNRESET, &newBehavior, sizeof(newBehavior),
NULL, 0, &retBytes, NULL, NULL);
增大收发缓冲(防抖动与瞬时拥塞):
int rcv = 4 * 1024 * 1024; // 4MB
int snd = 2 * 1024 * 1024; // 2MB
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv, sizeof(rcv));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&snd, sizeof(snd));
控制单包大小与出队节奏(配合 1200B 负载限制):
// 服务器广播时每帧限速,避免拥塞喷发
const int kBurstBudgetBytesPerTick = 64 * 1024; // 每逻辑tick最多出 64KB
弱网鲁棒:
- 重要帧(状态、结算)走小冗余 FEC(例如 10:1,应用层 Reed-Solomon 简单包裹)。
- 普通帧带 序号 与 滑动窗口重传(仅关键片段重传,窗口 ≤ 4)。
- 心跳 2s,三次丢失标记弱网降帧(服务器按房间动态降发频率)。
实战:把 SO_RCVBUF 从 256KB 拉到 4MB 后,server_recv_queue_drops 降了 70% 以上;再叠加小比例 FEC 后,玩家端“技能丢失体感”明显改善。
9) 日志与限流
将每个房间的出包速率、重传占比打印到 Prometheus,设置报警阈值。
当单地区突发抖动,服务端对该地区动态降码率/降帧率,保证可玩性优先于画质/频率。
L4 路由/线路策略:把“跨境这口气”理顺
10) 入口与出站分流
游戏端口段绑定到质量更好的上联(例如 PCCW):
如果你是多出口 NAT,可按五元组策略把 UDP/27015-30100 固定走 PCCW;语音可走 CMI(视实际)。
避免 ECMP 让同一房间包分散到不同出口(乱序 ↑)。在边界把该端口段的哈希改成 5-tuple并尽量固定到单链路。
11) 保留带宽
出口对游戏 DSCP=46 的队列保留 30% 峰值带宽,避免被日志/备份挤占。
12) (可选)就近 Relay/加速
我们额外在广州落了轻量 UDP Relay(同一协议、就近汇聚)——跨境的最后一跳从广州到香港,两端都可控,对远离骨干的玩家极其友好。
不想自建的,可以评估有信誉的“跨境精品回程”(买回程而不是回国专线),但先把前 11 步做到位再花钱,收益最稳。
可复用的自动化脚本
一键设置网卡与 QoS(PowerShell 片段):
$if = "Ethernet 2"
# 高性能电源
powercfg /setactive SCHEME_MIN | Out-Null
# RSS & Buffers
Enable-NetAdapterRss -Name $if
Set-NetAdapterRss -Name $if -BaseProcessorNumber 2 -MaxProcessors 6
Set-NetAdapterAdvancedProperty -Name $if -DisplayName "Receive Buffers" -DisplayValue "4096" -ErrorAction SilentlyContinue
Set-NetAdapterAdvancedProperty -Name $if -DisplayName "Transmit Buffers" -DisplayValue "2048" -ErrorAction SilentlyContinue
Set-NetAdapterAdvancedProperty -Name $if -DisplayName "Interrupt Moderation Rate" -DisplayValue "Adaptive" -ErrorAction SilentlyContinue
# 尝试开启 UDP 校验和卸载
"UDP Checksum Offload (IPv4)","UDP Checksum Offload (IPv6)" | %{
Set-NetAdapterAdvancedProperty -Name $if -DisplayName $_ -DisplayValue "Enabled" -ErrorAction SilentlyContinue
}
# MTU
netsh interface ipv4 set subinterface "$if" mtu=1500 store=persistent | Out-Null
# 防火墙
$ports = @("27015-27030","30000-30100","50000-50050")
foreach($p in $ports){
netsh advfirewall firewall add rule name="Game_UDP_$p" dir=in action=allow protocol=UDP localport=$p | Out-Null
netsh advfirewall firewall add rule name="Game_UDP_$p_out" dir=out action=allow protocol=UDP localport=$p | Out-Null
}
# QoS DSCP 标记
if (-not (Get-NetQosPolicy -Name "GameUDP" -ErrorAction SilentlyContinue)) {
New-NetQosPolicy -Name "GameUDP" -IPProtocolMatchCondition UDP -DestinationPortRange 27015-27030,30000-30100,50000-50050 -DSCPAction 46 | Out-Null
}
Write-Host "Baseline applied for $if"
指标对比(真实一晚的数据)
| 时间段 (HKT) | 优化项阶段 | UDP 平均丢包 | P95 抖动 | 长会话中断率 | 备注 |
|---|---|---|---|---|---|
| 20:00–20:30 | 调优前 | 6.7% | 18 ms | 2.4% | 广州/长沙尤甚 |
| 20:30–21:00 | L1 完成(驱动/RSS/缓冲) | 3.1% | 12 ms | 1.6% | Packets Discarded 降 60% |
| 21:00–21:30 | L2 完成(QoS/MTU/规则) | 1.4% | 10 ms | 1.1% | 峰值开始顶住 |
| 21:30–22:30 | L3 完成(缓冲+FEC+限速) | 0.6% | 8 ms | 0.7% | 玩家体感已明显 |
| 22:30–23:30 | L4 分流(固定优质出口) | 0.3% | 7 ms | 0.6% | 整体稳定 |
注:丢包与抖动按房间服汇总(Prometheus 指标),抽样覆盖 12,000+ 并发用户。
踩过的坑(替你省时间)
- X710 驱动版本与固件不匹配:RSS 队列异常只跑 2 队,DPC Rate 持续高。升级到与固件匹配版本后恢复。
- Interrupt Moderation 设太激进:为了压 CPU 抖动设成 High,导致语音端“掉字”更明显。改回 Adaptive、配合更大的接收缓冲才稳。
- ECMP 导致乱序:边界路由器默认基于 3-tuple 哈希,部分 UDP 流分散到不同出口 → 客户端重排不过来。固定端口段到单出口解决。
- 应用包太大:一次发 1400B 以上在个别跨境路径被碎片化,碎片丢任何一片都等于整包丢;把有效负载控到 ≤1200B 后大幅改善。
- SIO_UDP_CONNRESET 未关闭:对端偶尔发端口不可达,服务端 Recv 被 WSAECONNRESET 干扰,误判连接异常;关闭后恢复稳定。
- 防火墙规则过于宽泛:放行“程序”而非“端口”,在更新后命中复杂规则链,CPU 抖动提升。换成明确端口规则后好转。
复盘与验收 checklist
BIOS 电源策略 Performance,OS 高性能
网卡 RSS 开启、队列绑定与缓冲调优
Interrupt Moderation 设为 Adaptive,结合 DPC 曲线校正
MTU=1500(或按隧道开销下调),应用单包 ≤1200B
防火墙端口明确放行(入/出)
- QoS:游戏端口 DSCP=46,后台流量限速
- 应用:SO_RBUF ≥ 4MB,SO_SBUF ≥ 2MB,关闭 SIO_UDP_CONNRESET
- 应用:轻量 FEC + 关键片段重传 + 弱网降帧
- 出口:端口段固定优质线路,避免 ECMP 乱序
- 观测:Prometheus/PerfMon/PktMon 三位一体,留证据、可回归
凌晨一点半,风小了,警报灯也不再刺眼。运营在群里丢了一句:“投诉没了,广州的说不卡了。”我把最后一台机器的 PktMon 停掉,机房里只剩风口低鸣和交换机的指示灯在眨眼。
很多人把 UDP 当成“天生不可靠”,可我更愿意把它当成一只要驯服的野马:把底层接住、把栈里让路、把应用做好容错、把路由理顺 —— 它就能跑得又快又稳。希望这份记录,能在你下一次和“丢包”硬刚的时候,替你挡掉几个坑、少挨几通电话。
如果你正在香港上机器,或者已经上了却还在掉包,按照本文从 L1 到 L4 的顺序走一遍。先把能控的控住,再去挖线路的钱坑。