Zebra 游戏服务器 — RustProxy 中间件改造方案
目标:服务器完全处于 NAT4 无公网环境,通过 RustProxy 穿透对外服务
一、当前架构回顾
1.1 网络拓扑(改造前)
┌──────────┐ TCP:7000 ┌──────────┐
│ Client │───────────────→│ FLServer │ (登录验证)
└────┬─────┘ └──────────┘
│ │
│ TCP:GatewayExtPort │ 内部转发
▼ ▼
┌──────────┐ 内网TCP ┌──────────────┐
│ Client │──────────────→│GatewayServer │←→ SessionServer
└──────────┘ └──────┬───────┘←→ ScenesServer
│ ←→ BillServer
┌─────┴──────┐
│ SuperServer │ (中心注册)
└────────────┘
关键端口:
| 服务进程 | 端口 | 用途 |
|---|---|---|
| FLServer | 7000 | 客户端登录连接 |
| FLServer | 7001 | 内部服务间通信 |
| FLServer | 7002 | PING 服务(服务器列表查询) |
| GatewayServer | extPort(数据库配置) | 客户端游戏连接 |
| SuperServer | 10000 | 仅内网 |
| ZtLoginServer | 8003 | 独立登录/商城服务 |
| UserServer | HTTP API 端口 | 充值、用户查询等 Web API |
1.2 核心问题
服务器全部在内网 NAT4 之后,无公网 IP。客户端无法直连 FLServer:7000 和 GatewayServer:extPort。
二、RustProxy 工作原理
┌──────────────────── 公网 ────────────────────┐
│ │
│ ┌──────────────────────┐ │
│ │ RustProxy Server │ │
│ │ :7000 (隧道端口) │←── TLS 加密隧道 ──┐ │
│ │ :17000 (TCP代理公网) │ │ │
│ │ :17001 (TCP代理公网) │ │ │
│ │ :7500 (Web管理面板) │ │ │
│ └──────────┬───────────┘ │ │
└──────────────┼────────────────────────────────┼─┘
│ │
NAT4 防火墙 NAT4 防火墙
│ │
┌──────────────┼──── 内网 ─────────────────────┼─┐
│ ▼ │ │
│ ┌───────────────────┐ ┌───────────────┐ │ │
│ │ RustProxy Client │────→│ FLServer:7000 │ │ │
│ │ (主动连公网隧道) │ ├───────────────┤ │ │
│ └───────────────────┘ │ Gateway:extPort│ │ │
│ └───────────────┘ │ │
└──────────────────────────────────────────────┘ │
│
客户端 ──→ RustProxy公网端口 ──(TLS隧道)──→ 内网服务 │
└────────────────────────────────────────────────┘
核心机制:
- RustProxy Client 运行在内网,主动向公网 RustProxy Server 建立 TLS 隧道
- 外部用户连接 RustProxy Server 的公网端口
- RustProxy Server 通过 TLS 隧道通知 Client 建立工作连接
- 数据双向透传:外部用户 ↔ RustProxy Server ↔ TLS隧道 ↔ RustProxy Client ↔ 内网服务
- 支持 PROXY Protocol v1/v2 传递客户端真实 IP 给后端
三、需要暴露到公网的端口清单
| 服务进程 | 对外端口(原) | 协议类型 | 用途 |
|---|---|---|---|
| FLServer | 7000 | TCP | 客户端登录连接 |
| FLServer | 7002 | TCP | PING 服务(服务器列表查询) |
| GatewayServer | extPort(数据库配) | TCP | 客户端游戏连接 |
| ZtLoginServer | 8003 | TCP | 独立登录/商城服务 |
| UserServer | HTTP API 端口 | HTTP/TCP | 充值、用户查询等 Web API |
注意:
SuperServer:10000、FLServer:7001(内部)、以及所有子服务器间的内部通信端口(SessionServer、ScenesServer 等)无需穿透,它们在同一内网。
四、Server 端需要修改的内容
4.1 数据库 SERVERLIST 表修改(★ 最关键)
当前:EXTIP/EXTPORT 为服务器本机的外网映射地址
改后:EXTIP/EXTPORT 为域名(指向 RustProxy Server)和代理端口
需修改的字段:
EXTIP→ 域名(如s1.gp.cn),指向 RustProxy Server 公网地址EXTPORT→ RustProxy 为该服务分配的公网代理端口
示例 SQL(假设域名为 s1.gp.cn,指向 RustProxy Server 1):
-- FLServer 外网地址
UPDATE SERVERLIST SET
EXTIP = 's1.gp.cn',
EXTPORT = 7000 -- RustProxy 为 FLServer 登录端口分配的公网端口
WHERE TYPE = 10; -- LOGINSERVER
-- GatewayServer 外网地址
UPDATE SERVERLIST SET
EXTIP = 's1.gp.cn',
EXTPORT = 6020 -- RustProxy 为 GatewayServer 分配的公网端口
WHERE TYPE = 22; -- GATEWAYSERVER
链路说明:SuperServer 启动时从 SERVERLIST 读取这些值,在 t_Startup_Response 和 t_Startup_ServerEntry_NotifyMe/NotifyOther 中下发给各子服务器。FLServer 收到 GatewayServer 的 EXTIP:EXTPORT 后,在登录成功响应中返回给客户端,客户端据此连接 GatewayServer。
4.2 ServerEntry 结构无需代码修改
当前 zType.h 中 ServerEntry 已有 pstrExtIP[16] / wdExtPort 字段,天然支持外网地址映射。只要数据库值正确,整条链路自动生效:
数据库 → SuperServer → FLServer → 客户端 → 连接域名解析到的 RustProxy 公网地址
4.3 config.xml 无需修改
文件:server-code/config.xml
当前关键配置:
<superserver port="10000">127.0.0.1</superserver>
无需修改。SuperServer 在内网,子服务器通过内网连接它,不受 NAT 影响。
FLServer 端口配置也无需修改:
<login_port>7000</login_port>
<inside_port>7001</inside_port>
<ping_port>7002</ping_port>
RustProxy Client 的 local_ip:local_port 指向这些内网端口即可。
4.4 FLServer — 返回给客户端的网关地址
文件:server-code/FLServer/FLServer.cpp、server-code/FLServer/LoginManager.h/.cpp
当前逻辑:FLServer 通过 SuperServer 获取 GatewayServer 的 ServerEntry,将 pstrExtIP:wdExtPort 放入 t_NewLoginSession 返回给客户端。
修改:无需改代码。只要 SERVERLIST 表中 Gateway 的 EXTIP/EXTPORT 设为域名和 RustProxy 代理端口即可。客户端 CTCPSocket::Connect() 已内置 gethostbyname() 域名解析,天然支持域名。
4.5 GatewayServer — 客户端真实 IP 获取(★★ 重要)
当前问题:客户端通过 RustProxy 代理连接后,GatewayServer 看到的来源 IP 是 RustProxy Client 的内网 IP(如 127.0.0.1),而非客户端真实 IP。
影响范围:
t_NewLoginSession.client_ip[16]— 记录的客户端 IP 失真- 安全策略:IP 封禁、异常登录检测失效
- 日志追踪:无法定位真实用户
解决方案:启用 RustProxy 的 PROXY Protocol 功能
RustProxy 代理规则配置示例(Web API):
{
"name": "gateway",
"proxy_type": "tcp",
"client_id": "zebra-game-server",
"local_ip": "127.0.0.1",
"local_port": 7001,
"remote_port": 17001,
"proxy_protocol": "v1"
}
PROXY Protocol 会在 TCP 连接建立后、业务数据之前注入一行文本(v1) 或二进制头(v2),告知后端客户端真实 IP。
具体修改方案见 第六节。
4.6 FLServer — 同样需要 PROXY Protocol 解析
文件:server-code/FLServer/LoginTask.h/.cpp
LoginTask 也需要解析 PROXY Protocol 头来获取客户端真实 IP,用于登录日志和安全审计。修改方式同 GatewayTask。
4.7 ZtLoginServer — 独立登录服务
文件:server-code/LoginServer/ZtLoginServer.h/.cpp
配置:server-code/LoginServer/login_server.xml
如果 ZtLoginServer 也需要对外服务:
- 在 RustProxy 中添加 TCP 代理规则,指向内网 8003 端口
- 修改客户端配置中 ZtLoginServer 的地址为 RustProxy 公网地址
- 同样需要 PROXY Protocol 解析(如果需要真实 IP)
4.8 UserServer — HTTP API
文件:server-code/UserServer/UserServer.h/.cpp
如果 UserServer 的 HTTP API 需要对外:
- 在 RustProxy 中添加 HTTP 或 TCP 代理规则
- HTTP 模式可基于域名路由,共享 8080 端口
- 无需 PROXY Protocol,HTTP 头中已有
X-Forwarded-For等字段
4.9 服务器间通信 — 无需修改
以下通信全部在内网,不走 RustProxy:
- 子服务器 →
SuperServer:10000(注册/心跳) - GatewayServer → SessionServer(转发)
- GatewayServer → ScenesServer(转发)
- GatewayServer → BillServer(转发)
- GatewayServer → RecordServer(转发)
- ScenesServer → SessionServer(转发)
- FLServer → SuperServer(内部端口 7001)
4.10 心跳与超时配置 — 可能需要调整
文件:server-code/base/zSocket.h
当前超时参数:
| 参数 | 当前值 | 说明 |
|---|---|---|
T_RD_MSEC | 2100ms | 读取超时 |
T_WR_MSEC | 5100ms | 发送超时 |
经 RustProxy 中间代理后,网络延迟增加(TLS 隧道 + 双跳)。如果出现连接超时断开,需适当增大这些值。
文件:server-code/base/zTCPTask.h
- 链路检测信号间隔 600 秒,一般不受影响
RustProxy 自身心跳:
- Client 每 10 秒发 Ping,Server 90 秒超时
- 这些在 RustProxy 层面处理,不影响游戏层
五、Client 端需要的修改
5.1 config.ini
修改登录服务器地址为域名(指向 RustProxy 公网地址):
[Server]
loginAddress = s1.gp.cn:s2.gp.cn:s3.gp.cn
loginPort = 17000,17000,17000
zone = 1
说明:客户端
CTCPSocket::Connect()已内置gethostbyname()域名解析,无需任何代码修改即可支持域名连接。
5.2 代码无需修改
客户端连接流程不变:
- 连接
loginAddress:loginPort(现在是域名指向 RustProxy 公网地址) - 登录成功后收到
stServerReturnLoginSuccessCmd.pstrIP:wdPort(这是SERVERLIST表中配置的域名和 RustProxy 代理端口) - 连接 Gateway 的 RustProxy 公网地址(域名自动解析)
客户端完全无感知中间有代理,因为 RustProxy 做的是 TCP 透传。
六、PROXY Protocol 解析代码实现(C++98)
6.1 zSocket.h — 新增 PROXY Protocol 解析方法声明
文件:server-code/base/zSocket.h
在 zSocket 类中新增:
// PROXY Protocol 解析结果
struct ProxyProtocolInfo {
bool valid; // 是否解析成功
char srcIP[16]; // 客户端真实 IP
unsigned short srcPort; // 客户端真实端口
char dstIP[16]; // 目标 IP
unsigned short dstPort; // 目标端口
int version; // 1=v1, 2=v2
ProxyProtocolInfo() : valid(false), srcPort(0), dstPort(0), version(0) {
memset(srcIP, 0, sizeof(srcIP));
memset(dstIP, 0, sizeof(dstIP));
}
};
在 zSocket 类 public 区域新增:
bool parseProxyProtocol(ProxyProtocolInfo &info);
6.2 zSocket.cpp — PROXY Protocol 解析实现
文件:server-code/base/zSocket.cpp
bool zSocket::parseProxyProtocol(ProxyProtocolInfo &info) {
// v1 格式: "PROXY TCP4 1.2.3.4 5.6.7.8 12345 80\r\n"
// v2 格式: 12字节签名头 + 4字节头 + 变长地址
// 尝试 peek 数据(不从缓冲区移除),判断 v1 还是 v2
char peekBuf[256];
int peekLen = recv(_fd, peekBuf, sizeof(peekBuf), MSG_PEEK);
if (peekLen <= 0) return false;
// v2 签名: \x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A
static const char v2sig[12] = {
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A
};
if (peekLen >= 12 && memcmp(peekBuf, v2sig, 12) == 0) {
// PROXY Protocol v2 (二进制)
if (peekLen < 16) return false;
unsigned char ver_cmd = peekBuf[12];
unsigned char ver = ver_cmd >> 4;
unsigned char cmd = ver_cmd & 0x0F;
if (ver != 2) return false;
unsigned char fam = peekBuf[13];
unsigned short addrLen = ntohs(*(unsigned short*)(peekBuf + 14));
int totalLen = 16 + addrLen;
if (peekLen < totalLen) {
return false;
}
if (cmd == 0) {
// LOCAL 连接,不修改
recv(_fd, peekBuf, totalLen, 0);
info.valid = true;
info.version = 2;
return true;
}
// PROXY 连接
char *addrData = peekBuf + 16;
unsigned char proto = fam & 0x0F;
if (proto == 1) {
// TCP4
struct InAddr { unsigned char b[4]; };
InAddr *src = (InAddr*)addrData;
InAddr *dst = (InAddr*)(addrData + 4);
unsigned short *ports = (unsigned short*)(addrData + 8);
snprintf(info.srcIP, sizeof(info.srcIP), "%u.%u.%u.%u",
src->b[0], src->b[1], src->b[2], src->b[3]);
snprintf(info.dstIP, sizeof(info.dstIP), "%u.%u.%u.%u",
dst->b[0], dst->b[1], dst->b[2], dst->b[3]);
info.srcPort = ntohs(ports[0]);
info.dstPort = ntohs(ports[1]);
} else if (proto == 2) {
// TCP6
snprintf(info.srcIP, sizeof(info.srcIP), "[IPv6]");
snprintf(info.dstIP, sizeof(info.dstIP), "[IPv6]");
}
recv(_fd, peekBuf, totalLen, 0);
info.valid = true;
info.version = 2;
return true;
} else if (peekLen >= 6 && memcmp(peekBuf, "PROXY ", 6) == 0) {
// PROXY Protocol v1 (文本)
// 手动查找 \r\n 结束标记(兼容 GCC 3.4.6,不依赖 memmem)
int headerLen = -1;
for (int i = 0; i < peekLen - 1; ++i) {
if (peekBuf[i] == '\r' && peekBuf[i + 1] == '\n') {
headerLen = i + 2;
break;
}
}
if (headerLen < 0) {
if (peekLen >= 108) return false; // v1 最大 108 字节
return false;
}
peekBuf[headerLen - 2] = '\0'; // 截断 \r\n
// 解析: PROXY TCP4 srcIP dstIP srcPort dstPort
char proto[8], srcIP[16], dstIP[16];
unsigned short srcPort, dstPort;
if (sscanf(peekBuf, "PROXY %7s %15s %15s %hu %hu",
proto, srcIP, dstIP, &srcPort, &dstPort) == 5) {
strncpy(info.srcIP, srcIP, sizeof(info.srcIP) - 1);
strncpy(info.dstIP, dstIP, sizeof(info.dstIP) - 1);
info.srcPort = srcPort;
info.dstPort = dstPort;
info.valid = true;
info.version = 1;
} else if (memcmp(peekBuf, "PROXY UNKNOWN", 13) == 0) {
info.valid = true;
info.version = 1;
}
recv(_fd, peekBuf, headerLen, 0);
return info.valid;
}
// 不是 PROXY Protocol,直接返回(直连模式)
return false;
}
6.3 GatewayTask.h — 修改 verifyConn 解析真实 IP
文件:server-code/GatewayServer/GatewayTask.h
在 GatewayTask 类中添加成员:
char m_strRealClientIP[16]; // 通过 PROXY Protocol 获取的真实客户端 IP
构造函数中初始化:
m_strRealClientIP[0] = '\0';
6.4 GatewayTask.cpp — verifyConn 中调用解析
文件:server-code/GatewayServer/GatewayTask.cpp
在 GatewayTask::verifyConn() 方法开头添加:
// 解析 PROXY Protocol 获取真实客户端 IP
zSocket::ProxyProtocolInfo ppInfo;
if (pSocket->parseProxyProtocol(ppInfo) && ppInfo.valid) {
strncpy(m_strRealClientIP, ppInfo.srcIP, sizeof(m_strRealClientIP) - 1);
m_strRealClientIP[sizeof(m_strRealClientIP) - 1] = '\0';
}
后续所有使用客户端 IP 的地方(如 t_NewLoginSession.client_ip),改为使用 m_strRealClientIP(非空时优先,为空时回退到 socket 对端 IP)。
6.5 FLServer/LoginTask — 同样修改
文件:server-code/FLServer/LoginTask.h/.cpp
同 GatewayTask 的修改方式,在 LoginTask::verifyConn() 中解析 PROXY Protocol,替换来源 IP 记录。
6.6 编译注意事项
memmem()不是 C98 标准函数,已改用手动查找\r\n,兼容 GCC 3.4.6MSG_PEEK在 Winsock 和 Linux 下均可用- 确保
#include <sys/socket.h>(Linux)或<winsock2.h>(Windows) - PROXY Protocol 头必须在
verifyConn()阶段消费掉,后续正常解析游戏包
七、域名 DNS 调度方案
7.1 域名规划
利用短根域名 gp.cn(示范),每个子域指向一台独立的 RustProxy Server,子域长度在 MAX_IP_LENGTH=16 字节限制内:
| 子域 | 字符数 | 指向 | 用途 |
|---|---|---|---|
s1.gp.cn | 7 | RustProxy Server 1 公网 IP | 线路1 |
s2.gp.cn | 7 | RustProxy Server 2 公网 IP | 线路2 |
s3.gp.cn | 7 | RustProxy Server 3 公网 IP | 线路3 |
即使更长的根域也够用:s1.game.cn(9)、s1.gproxy.cn(11)、s1.gproxy.com(12)、s1.game-proxy.cn(14),均在 16 字节以内。
7.2 DNS 配置
; 每个子域指向一台 RustProxy Server
s1.gp.cn A 1.2.3.4 ; RustProxy Server 1
s2.gp.cn A 5.6.7.8 ; RustProxy Server 2
s3.gp.cn A 9.10.11.12 ; RustProxy Server 3
; TTL 设短(60s),故障时快速切换 DNS
; 也可为同一子域配多个 A 记录做 DNS 轮询
s1.gp.cn A 1.2.3.4
s1.gp.cn A 1.2.3.5 ; 同线路备用 IP
7.3 数据库 SERVERLIST 配置
-- FLServer 外网地址 → 指向 RustProxy Server 1
UPDATE SERVERLIST SET
EXTIP = 's1.gp.cn',
EXTPORT = 17000
WHERE TYPE = 10; -- LOGINSERVER
-- GatewayServer 外网地址 → 指向 RustProxy Server 1
UPDATE SERVERLIST SET
EXTIP = 's1.gp.cn',
EXTPORT = 17001
WHERE TYPE = 22; -- GATEWAYSERVER
7.4 客户端多线路容灾配置
利用客户端已有的多地址(冒号分隔)+ 多端口(逗号分隔)重试机制:
[Server]
; 三条线路自动切换,DNS 层面再做 A 记录轮询,双重冗余
loginAddress = s1.gp.cn:s2.gp.cn:s3.gp.cn
loginPort = 17000,17000,17000
zone = 1
容灾流程:
1. 客户端尝试连接 s1.gp.cn:17000
→ DNS 解析得到 RustProxy Server 1 的 IP
→ 连接成功 → 正常游戏
2. 如果 RustProxy Server 1 挂了
→ 连接失败 → 客户端自动重试 s2.gp.cn:17000
→ DNS 解析得到 RustProxy Server 2 的 IP
→ 连接成功 → 切换到线路2
3. 同一线路内 DNS 也有多 A 记录轮询
→ 单 IP 故障时 DNS 返回其他可用 IP
7.5 域名兼容性验证
| 组件 | 域名支持 | 说明 |
|---|---|---|
客户端 CTCPSocket::Connect() | ✅ 已支持 | 内置 inet_addr() + gethostbyname() 回退 |
服务器 zTCPClientTask | ✅ 不涉及 | 内网通信用 pstrIP,不用 pstrExtIP |
MAX_IP_LENGTH=16 | ✅ 足够 | s1.gp.cn 仅 7 字节 |
| FLServer → 客户端下发 | ✅ 透传 | 域名作为字符串传递,客户端解析 |
八、RustProxy 部署配置
8.1 RustProxy Server 配置(公网机器)
文件:configs/server.toml
[server]
bind_addr = "0.0.0.0"
bind_port = 7000 # 隧道端口
token = "<安全的Token>"
http_port = 0 # 不需要 HTTP 代理
https_port = 0 # 不需要 HTTPS 代理
[web]
enable = true
bind_addr = "0.0.0.0"
bind_port = 7500
user = "admin"
password = "<bcrypt密码>"
[tls]
auto_cert = true # 开发用自签证书,生产环境用正式证书
8.2 RustProxy Client 配置(内网机器)
文件:configs/client.toml
[client]
id = "zebra-game-server"
server_addr = "s1.gp.cn" # 或直接填 RustProxy Server 公网 IP
server_port = 7000
token = "<与 Server 端一致>"
ca_cert = "" # 自签证书时跳过验证
server_name = ""
8.3 代理规则(通过 Web API 创建)
POST http://<RustProxy_Server>:7500/api/proxies
规则1:FLServer 登录端口
{
"name": "fl-login",
"proxy_type": "tcp",
"client_id": "zebra-game-server",
"local_ip": "127.0.0.1",
"local_port": 7000,
"remote_port": 17000,
"proxy_protocol": "v1"
}
规则2:FLServer PING 端口
{
"name": "fl-ping",
"proxy_type": "tcp",
"client_id": "zebra-game-server",
"local_ip": "127.0.0.1",
"local_port": 7002,
"remote_port": 17002,
"proxy_protocol": ""
}
规则3:GatewayServer 游戏端口
{
"name": "gateway-game",
"proxy_type": "tcp",
"client_id": "zebra-game-server",
"local_ip": "127.0.0.1",
"local_port": 7001,
"remote_port": 17001,
"proxy_protocol": "v1"
}
规则4:ZtLoginServer(如需对外)
{
"name": "zt-login",
"proxy_type": "tcp",
"client_id": "zebra-game-server",
"local_ip": "127.0.0.1",
"local_port": 8003,
"remote_port": 18003,
"proxy_protocol": "v1"
}
九、完整数据流(改造后)
登录阶段
┌────────┐ TCP:s1.gp.cn:17000 ┌────────────┐ TLS隧道 ┌──────────────┐ TCP:7000 ┌──────────┐
│ Client │────────────────────→│RustProxy │─────────→│RustProxy │──────────→│ FLServer │
│ │←───────────────────│Server(公网) │←─────────│Client(内网) │←──────────│ │
└────────┘ └────────────┘ └──────────────┘ └──────────┘
↑ DNS解析 s1.gp.cn → RustProxy Server IP
↑ PROXY Protocol v1 头: srcIP=Client真实IP
│ FLServer 解析后获得真实 IP
游戏阶段
┌────────┐ TCP:s1.gp.cn:17001 ┌────────────┐ TLS隧道 ┌──────────────┐ TCP:内网端口 ┌──────────┐
│ Client │────────────────────→│RustProxy │─────────→│RustProxy │──────────→│Gateway │
│ │←───────────────────│Server(公网) │←─────────│Client(内网) │←──────────│Server │
└────────┘ └────────────┘ └──────────────┘ └────┬─────┘
↑ DNS解析 s1.gp.cn → RustProxy Server IP │内网
↑ PROXY Protocol v1 头: srcIP=Client真实IP ┌────────────┼────────────┐
│ GatewayServer 解析后获得真实 IP ▼ ▼ ▼
SessionServer ScenesServer BillServer
服务器间通信(不变,纯内网)
SuperServer ↔ SessionServer ↔ ScenesServer ↔ RecordServer ↔ BillServer ↔ MiniServer
十、启动顺序(改造后)
- 启动 RustProxy Server(公网机器)
- 启动 RustProxy Client(内网机器),连接公网隧道
- 通过 Web API 创建所有代理规则(或预先在 SQLite 中配好)
- 按原顺序启动游戏服务器集群:
SuperServer → RecordServer → BillServer → MiniServer →SessionServer → ScenesServer → GatewayServer → FLServer
- 启动 ZtLoginServer、UserServer(如需要)
- 客户端配置
loginAddress为域名,启动游戏
十一、风险与注意事项
11.1 延迟增加
原本直连变为:Client → RustProxy Server → TLS → RustProxy Client → GameServer
增加约 1-2 跳网络延迟 + TLS 加解密开销。对于 MMO 游戏影响可接受(心跳间隔 600 秒,非 FPS 级实时)。
11.2 带宽瓶颈
所有玩家流量经过 RustProxy Server 公网机器,需确保带宽充足。
| 并发玩家数 | 人均带宽 | 总带宽需求 |
|---|---|---|
| 1000 | 5 KB/s | ~40 Mbps |
| 2000 | 5 KB/s | ~80 Mbps |
| 5000 | 5 KB/s | ~200 Mbps |
11.3 单点故障与域名容灾
RustProxy Server 单机挂掉则该线路断线。通过域名多线路方案解决:
- 客户端
loginAddress配置多个域名(s1.gp.cn:s2.gp.cn:s3.gp.cn),自动重试 - DNS 同一子域可配多个 A 记录做轮询
- 建议至少部署 2 台 RustProxy Server 实现冗余
11.4 加密叠加
游戏协议本身已有 RC5/DES 加密,RustProxy 又加了 TLS。双重加密不影响功能,但增加 CPU 开销。可评估是否需要简化游戏层加密(不推荐,安全性降级)。
11.5 PROXY Protocol 兼容性
- 必须确保 RustProxy 代理规则中
proxy_protocol设置与服务器解析代码一致 - 推荐使用 v1(文本格式),调试方便,兼容性好
- 如果直连模式(不经过代理)也需要支持,服务器代码中
parseProxyProtocol返回false时应使用 socket 对端 IP
11.6 数据包边界问题
RustProxy 是 TCP 透传,不解析游戏协议,不会破坏包边界。但 PROXY Protocol 头是在连接建立后注入的首段数据,必须在 zTCPTask::verifyConn() 阶段消费掉,后续正常解析游戏包。
11.7 数据库 SERVERLIST 多区配置
如果多个游戏区共享同一 RustProxy Server,每个区的 Gateway 需要不同的公网端口,避免冲突。
11.8 DNS 缓存与 TTL
- 客户端
gethostbyname()的 DNS 缓存由操作系统管理,受 TTL 影响 - 建议域名 TTL 设为 60 秒,故障切换时生效更快
- Windows 默认 DNS 缓存时间较长,可考虑客户端侧定期刷新
十二、修改清单汇总
| 序号 | 修改文件 | 修改类型 | 说明 |
|---|---|---|---|
| 1 | 数据库 SERVERLIST 表 | 数据修改 | EXTIP → 域名(如 s1.gp.cn),EXTPORT → RustProxy 代理端口 |
| 2 | base/zSocket.h | 新增代码 | ProxyProtocolInfo 结构体 + parseProxyProtocol 声明 |
| 3 | base/zSocket.cpp | 新增代码 | parseProxyProtocol 实现(v1/v2 解析) |
| 4 | GatewayServer/GatewayTask.h/.cpp | 修改代码 | verifyConn() 中调用 parseProxyProtocol,记录真实 IP |
| 5 | FLServer/LoginTask.h/.cpp | 修改代码 | 同上,LoginTask::verifyConn 解析真实 IP |
| 6 | config.xml | 无需修改 | 内网配置不变 |
| 7 | 客户端 config.ini | 配置修改 | loginAddress → 多域名(如 s1.gp.cn:s2.gp.cn:s3.gp.cn) |
| 8 | DNS A 记录 | 新增配置 | 每个子域指向一台 RustProxy Server 公网 IP |
| 9 | LoginServer/ZtLoginServer.h/.cpp | 可选修改 | 如 ZtLoginServer 需对外,添加 PROXY Protocol 解析 |
| 10 | UserServer/UserServer.h/.cpp | 可选修改 | HTTP API 如需对外,配置 RustProxy HTTP 代理规则 |
| 11 | base/zSocket.h | 可选修改 | 超时参数调整(仅在网络不稳定时) |
实施:
- 核心代码修改仅 3 处(
zSocket解析 +GatewayTask+LoginTask) - 最关键的是数据库
SERVERLIST表的域名配置 - 域名 DNS 调度实现多 RustProxy Server 负载均衡与容灾,客户端零代码修改
- 客户端仅改
config.ini配置文件,天然支持域名解析