FPS类游戏在香港服务器的 Windows Server 上如何设置和优化 UDP 协议栈,减少跨境玩家竞技射击延迟?

凌晨 1:12,香港游戏服务器的玩家群里还在刷屏:“重庆到香港今晚卡成 PPT”“广州抖 p95 200ms+”。我把 KVM 拉到前台,看着那台跑 FPS 专服的 Windows Server 2022:CPU 并不高,但 DPC 抖得厉害,网卡中断像小鼓点。跨境 UDP 的延迟和抖动,在国内黄金时段被无限放大。
这篇,就是那一夜我从“现象—定位—优化—复测—回滚预案”一路走下来的完整记录。写给同样在机房里和延迟较劲的人。
场景设定与目标
玩家分布:华南、华东为主(电信/联通/移动),也有少量东南亚。
机房:香港(HK),上联双线:CT/CU(含 CN2 优先策略),10GbE 接入。
主机(实配示例):
- 机型:Dell R650 / R7525(二选一皆可,以下以 Intel 为例)
- CPU:Xeon Silver/Gold 24C+(NUMA=2)
- 内存:128–256GB
- 网卡:Intel X710/XXV710 10/25GbE(驱动/FW 当月版)
- 系统盘:NVMe
操作系统:Windows Server 2022 Datacenter(2022 开始支持 USO:UDP Segmentation Offload,UDP 性能提升明显。2019 也能做但少一项利器)
游戏服务:典型 FPS 专服(64/128 tick),主要 UDP 端口:27015/27016(示例)
优化目标:在晚高峰(20:00–23:00)把 RTT p95 从 120–180ms 压到 90–120ms,同时把 抖动(Jitter)p95 降 20–40%;服务器侧 DPC% 降到 2% 以下,丢包<0.2%。
1. 我如何拆解“延迟”——先量化,后动刀
延迟并不是一个数,它由三部分叠加:
- 网络路径 RTT(公网、跨境、拥塞/绕路):我们能“影响但不完全掌控”;
- 服务器网络栈 & NIC(中断、队列、亲和、缓冲、offload):我们能“深度掌控”;
- 游戏进程排队(tick、线程调度、锁、GC):我们能“直接优化”。
基线采集(优化前)
我用下面这套最小闭环,每次动一项、复测一轮,避免“玄学调参”。
连通/路径:tracert -d <玩家探针IP>、pathping(看跨境段丢包/绕路)
UDP 时延/抖动:建议用 Sysinternals psping(客户端侧):
psping -u <server_ip>:27015 -n 500 -l 128(取 p50/p95/p99)
服务器指标(PerfMon):
\Processor(_Total)\% DPC Time、Interrupts/sec
\Network Interface(*)\Output Queue Length、Packets Received/Outbound Errors
\UDPv4\Datagrams Received Errors
抓包:Wireshark + pktmon(定位是否有重传/乱序/ICMP 报错)
示例基线(优化前)
(来自广州/杭州/成都三地公网探针,晚高峰 21:00–22:00)
| 地区 | RTT p50 | RTT p95 | Jitter p95 | 丢包 | 备注 |
|---|---|---|---|---|---|
| 广州(CT) | 83ms | 142ms | 21ms | 0.6% | 路由走非 CN2,晚高峰拥塞 |
| 杭州(CU) | 78ms | 128ms | 18ms | 0.3% | 稳定,轻微波动 |
| 成都(CM) | 96ms | 181ms | 34ms | 0.9% | 高抖动,链路变化频繁 |
| 服务器侧 DPC% | — | 4.7% | — | — | 中断聚合显著 |
2. Windows + NIC:把 UDP 路打通(核心实操)
先驱动/FW升到当月稳定版,再进系统调优。每步动前先导出配置,能回滚。
2.1 快速体检(PowerShell)
# 列 NIC、驱动、RSS 状态
Get-NetAdapter | ft Name, Status, LinkSpeed, DriverVersion, DriverInformation
Get-NetAdapterRss -Name "Ethernet*"
Get-NetAdapterAdvancedProperty -Name "Ethernet*" |
ft Name, DisplayName, DisplayValue -AutoSize
Get-NetOffloadGlobalSetting
2.2 RSS / 队列亲和(减少排队 & 抢占)
开启 RSS,让接收队列分摊到多核;把队列固定在同一 NUMA,减少跨节点访存。
实践值:8 队列起(10GbE),按物理核数灵活上限。
# 开启 RSS,并限定处理器范围(落在 NUMA0)
Enable-NetAdapterRss -Name "Ethernet 10G"
Set-NetAdapterRss -Name "Ethernet 10G" -MaxProcessors 8 -BaseProcessorNumber 0
Get-NetAdapterRss -Name "Ethernet 10G"
如果你的游戏进程固定在 NUMA0,RSS 也亲和 NUMA0,L3 命中率与抖动会更友好。
2.3 中断调制(Interrupt Moderation)——为延迟让路
许多网卡默认 Adaptive/Enabled,吞吐漂亮但引入 1–5ms 级额外延迟。
FPS 场景我倾向 关闭或设置为 Low Latency。
# 常见高级属性名随驱动不同而异,请以 Get-NetAdapterAdvancedProperty 查询结果为准
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "Interrupt Moderation" -DisplayValue "Disabled"
# 如果驱动提供单独的 "Interrupt Moderation Rate"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "Interrupt Moderation Rate" -DisplayValue "Low"
2.4 接收/发送环形缓冲(避免突刺掉包)
有的 X710/BCM 允许把 Receive Buffers / Transmit Buffers 从 512/1024 拉到 2048/4096。
这不是“越大越好”,但能把黄金时段的突发抖动吃住。
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "Receive Buffers" -DisplayValue "4096"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "Transmit Buffers" -DisplayValue "4096"
2.5 Offload:让硬件替你干活(USO/Checksum)
Win Server 2022 起,很多驱动支持 USO(UDP Segmentation Offload);
在高 PPS 场景能显著降低 CPU/DPC。
同时保持 UDP Checksum Offload(IPv4/IPv6)启用,减少内核开销。
# 视驱动暴露情况而定(有的显示为 “UDP Segmentation Offload”)
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "UDP Checksum Offload (IPv4)" -DisplayValue "Enabled"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "UDP Checksum Offload (IPv6)" -DisplayValue "Enabled"
# 若有 USO 选项:
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "UDP Segmentation Offload" -DisplayValue "Enabled"
注意:个别老交换机/老驱动组合对新 offload 不友好,打开后若出现丢包/乱序,立即回滚。
2.6 省电相关(EEE、节能以延迟为代价)
# 禁用 EEE / 节能
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" `
-DisplayName "Energy Efficient Ethernet" -DisplayValue "Disabled"
# 让系统处于高性能电源计划
powercfg /setactive SCHEME_MAX
# 可选:减少计时器合并(慎用,需重启)
bcdedit /set disabledynamictick yes
2.7 MTU 与碎片(用“更小的游戏包”而不是“更小的链路 MTU”)
跨境路径上常有 PPPoE/隧道,路径 MTU 往往低于 1500。
不建议把服务器 NIC MTU 改小(影响全局),更建议在游戏层控制单包大小(见 3.2)。
排查路径 MTU 的简单法(IPv4):
# 以 1472(=1500-20-8)开始,逐步减到 1400/1280,直到不再“需要分片”
ping -f -l 1472 <玩家探针IP>
3. 游戏服务进程:UDP 套接字到位,Tick 不背锅
3.1 忽略 UDP “连接被重置”陷阱(SIO_UDP_CONNRESET)
高并发 UDP 服务端最讨厌的错误之一:收到ICMP Port Unreachable 后,recvfrom 直接报 WSAECONNRESET,导致本不该失败的收包路径被打断。用这段代码关掉:
// C/C++:禁用 UDP “连接重置”行为
BOOL bNewBehavior = FALSE;
DWORD dwBytesReturned = 0;
WSAIoctl(udpSocket, SIO_UDP_CONNRESET,
&bNewBehavior, sizeof(bNewBehavior),
NULL, 0, &dwBytesReturned, NULL, NULL);
3.2 控制数据报大小(避免路径碎片 & CGNAT 痛点)
实战里我把有效载荷控制在 1200–1400 字节(含自定义头),与 QUIC 的 1200 类似。
打包策略:把高频“小报文”合并到 tick 周期内的单次发送,但避免超过 1400。
3.3 合理的 Socket 缓冲
int snd = 1 * 1024 * 1024; // 1MB
int rcv = 1 * 1024 * 1024;
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char*)&snd, sizeof(snd));
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char*)&rcv, sizeof(rcv));
备注:Windows 会翻倍/对齐缓冲。用 getsockopt 回读确认真实值。
缓冲不是越大越好,盯住延迟与丢包,再微调。
3.4 IO 模型与线程
Windows 下IOCP + Overlapped 对 UDP 同样友好,少上下文切换。
线程亲和到 RSS 相同 NUMA;tick 线程高优先级,I/O 线程正常优先级。
# 进程优先级(示例)
$proc = Get-Process -Name "gameserver"
$proc.PriorityClass = "High"
4. QoS(可选):DSCP 标记只在“你能控制的网络”里生效
公网跨境大多会清 DSCP,但机房内/上联第 1 跳通常能保留。
我给游戏进程打 DSCP 46 (EF),在 ToR/边界队列里能少排会儿队。
New-NetQosPolicy -Name "GameUDP" `
-AppPathNameMatchCondition "C:\Game\server\server.exe" `
-IPProtocolMatchCondition UDP -DSCPAction 46
若你能控制上联路由(或租到保 DSCP 的专线),收益可观;否则当作“顺手优化”。
5. 防火墙与最小权限
# 允许入站 UDP 27015/27016
New-NetFirewallRule -DisplayName "FPS-UDP-In" `
-Direction Inbound -Protocol UDP -LocalPort 27015-27016 -Action Allow
# 仅开放管理段到 RDP/WinRM,其他来源拒绝
# ……(按你的安全基线执行)
UDP 暴露面大,务必配合黑洞路由/清洗/ACL。
我们在 HK 的上联做了基础UDP Flood 防护,否则“优化好的延迟”会在打击中变成“抖动放大器”。
6. 路由与线路小技巧(不越界瞎搞,但能影响走向)
就近跨境:能上 CN2 优先 的供应商优先;联通/移动对应选好回程策略。
GSLB/就近入口:如果你有多台 HK 机,按运营商/地域智能调度,哪怕是 DNS 级,也能显著降低尾部延迟。
不建议在公网强行改 TTL/ECN 等“黑魔法”。
7. 一张“变更清单+回滚点”表(我当晚用的)
| 类别 | 项 | 新值 | 回滚点 |
|---|---|---|---|
| 网卡 | RSS | 启用,MaxProcessors=8,Base=0 | Disable-NetAdapterRss |
| 网卡 | Interrupt Moderation | Disabled / Low | 恢复默认(Adaptive) |
| 网卡 | Rx/Tx Buffers | 4096 / 4096 | 1024 / 1024 |
| 网卡 | UDP Offload | Checksum=Enabled,USO=Enabled(若有) | 关闭 USO/保持 Checksum |
| 网卡 | EEE | Disabled | Enabled |
| 系统 | 电源计划 | High Performance | Balanced |
| 系统 | Dynamic Tick | 可选关闭 | bcdedit /deletevalue disabledynamictick |
| 应用 | SIO_UDP_CONNRESET | 关闭 | 恢复默认 |
| 应用 | 单报文大小 | 1200–1400 | 放宽至 1472 |
| 安全 | 防火墙 | 仅放行游戏端口 | 恢复前策略 |
| QoS | DSCP | EF(46) | 删除策略 |
8. 复测数据(优化后)
同样时段(21:00–22:00),同样探针,参数逐项落实后复测。
| 地区 | RTT p50 | RTT p95 | Jitter p95 | 丢包 | 改善 |
|---|---|---|---|---|---|
| 广州(CT) | 74ms | 118ms | 12ms | 0.2% | p95 -24ms / 抖动 -9ms |
| 杭州(CU) | 72ms | 104ms | 11ms | 0.15% | 稳健 |
| 成都(CM) | 88ms | 139ms | 19ms | 0.35% | 抖动显著改善 |
| 服务器侧 DPC% | — | 1.6% | — | — | 中断/队列平稳 |
体感上,玩家“瞬狙”和“甩枪”反馈更扎实;服务端命中判定‘误差’申诉明显下降。
9. 当晚踩过的坑 & 现场解法
驱动显示名不一致:同是 X710,属性名里有的叫 “Interrupt Moderation Rate”,有的只有 “Interrupt Moderation”。
做法:先 Get-NetAdapterAdvancedProperty 把名字抄出来再改,别想当然。
开了 USO 反而偶发掉包:老边界交换机对新 offload 不友好。
做法:先只开 Checksum Offload,USO 回滚,观察 24h 再决策。
Hyper-V vSwitch 额外开销:虚拟化环境下 vSwitch + 安全策略会带来额外抖动。
做法:能上 SR-IOV 就上,或把专服放 裸金属。
电源计划被补丁还原:一次重启后变回 Balanced。
做法:纳入开机脚本 & 配置基线,定时审计。
路径 MTU 低估:把 NIC MTU 改小导致其他服务受影响。
做法:回滚 NIC MTU,在应用层把单包压到 1200–1400。
DSCP 不生效:公网清零。
做法:只在机房内部使用,用于边界排队,别指望跨境 ISP。
10. 附:常用命令清单(便于复制)
# 基线查看
Get-NetAdapter; Get-NetAdapterRss -Name "Ethernet 10G"
Get-NetAdapterAdvancedProperty -Name "Ethernet 10G" | ft DisplayName,DisplayValue
Get-NetOffloadGlobalSetting
# RSS
Enable-NetAdapterRss -Name "Ethernet 10G"
Set-NetAdapterRss -Name "Ethernet 10G" -MaxProcessors 8 -BaseProcessorNumber 0
# 中断与缓冲
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "Interrupt Moderation" -DisplayValue "Disabled"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "Receive Buffers" -DisplayValue "4096"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "Transmit Buffers" -DisplayValue "4096"
# Offload
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "UDP Checksum Offload (IPv4)" -DisplayValue "Enabled"
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "UDP Checksum Offload (IPv6)" -DisplayValue "Enabled"
# 若驱动支持:
# Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "UDP Segmentation Offload" -DisplayValue "Enabled"
# 省电与电源
Set-NetAdapterAdvancedProperty -Name "Ethernet 10G" -DisplayName "Energy Efficient Ethernet" -DisplayValue "Disabled"
powercfg /setactive SCHEME_MAX
# 防火墙
New-NetFirewallRule -DisplayName "FPS-UDP-In" -Direction Inbound -Protocol UDP -LocalPort 27015-27016 -Action Allow
# QoS(可选)
New-NetQosPolicy -Name "GameUDP" -AppPathNameMatchCondition "C:\Game\server\server.exe" -IPProtocolMatchCondition UDP -DSCPAction 46
11. 复盘与建议(给后来者)
- 先测后调:把 p50/p95/p99、丢包和 DPC% 固定成你的“三件套”。
- 一次只动一项:RSS→中断→缓冲→Offload→应用层→QoS,不要一把梭。
- 进程与 NUMA 一致性:网络队列在 NUMA0,进程也在 NUMA0,抖动立降。
- 黄金时段回归测试:白天顺不代表晚上也顺。
- 写好回滚:把每一项的“前后值”都记在变更单里(上面的表就是模板)。
收尾:那一夜之后
2:03,我把最后一条 DSCP 策略敲进去了。
2:17,广州探针一条干净的曲线贴着 100ms 划过去;成都的抖动也不再像心电图。玩家群里有人发了个“瞬狙合集”,弹道像轨道一样稳。
我在机房门口喝完那罐已经不冰的可乐,把变更和回滚拍成照片传进维保群,给自己留了一句注释:
“香港是地理,就近是科学,UDP 是手艺。”
你我这样的运维,多半也只是把手艺打磨到足够可靠,然后静静看着延迟线往下掉FPS类游戏在香港服务器的 Windows Server 上如何设置和优化 UDP 协议栈,减少跨境玩家竞技射击延迟?——一份来自机房的实操笔记