跳到主要内容

Zebra 游戏服务器 — RustProxy 中间件改造方案

目标:服务器完全处于 NAT4 无公网环境,通过 RustProxy 穿透对外服务


一、当前架构回顾

1.1 网络拓扑(改造前)

┌──────────┐ TCP:7000 ┌──────────┐
│ Client │───────────────→│ FLServer │ (登录验证)
└────┬─────┘ └──────────┘
│ │
│ TCP:GatewayExtPort │ 内部转发
▼ ▼
┌──────────┐ 内网TCP ┌──────────────┐
│ Client │──────────────→│GatewayServer │←→ SessionServer
└──────────┘ └──────┬───────┘←→ ScenesServer
│ ←→ BillServer
┌─────┴──────┐
│ SuperServer │ (中心注册)
└────────────┘

关键端口

服务进程端口用途
FLServer7000客户端登录连接
FLServer7001内部服务间通信
FLServer7002PING 服务(服务器列表查询)
GatewayServerextPort(数据库配置)客户端游戏连接
SuperServer10000仅内网
ZtLoginServer8003独立登录/商城服务
UserServerHTTP API 端口充值、用户查询等 Web API

1.2 核心问题

服务器全部在内网 NAT4 之后,无公网 IP。客户端无法直连 FLServer:7000GatewayServer: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 给后端

三、需要暴露到公网的端口清单

服务进程对外端口(原)协议类型用途
FLServer7000TCP客户端登录连接
FLServer7002TCPPING 服务(服务器列表查询)
GatewayServerextPort(数据库配)TCP客户端游戏连接
ZtLoginServer8003TCP独立登录/商城服务
UserServerHTTP API 端口HTTP/TCP充值、用户查询等 Web API

注意SuperServer:10000FLServer: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_Responset_Startup_ServerEntry_NotifyMe/NotifyOther 中下发给各子服务器。FLServer 收到 GatewayServer 的 EXTIP:EXTPORT 后,在登录成功响应中返回给客户端,客户端据此连接 GatewayServer。

4.2 ServerEntry 结构无需代码修改

当前 zType.hServerEntry 已有 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.cppserver-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 也需要对外服务:

  1. 在 RustProxy 中添加 TCP 代理规则,指向内网 8003 端口
  2. 修改客户端配置中 ZtLoginServer 的地址为 RustProxy 公网地址
  3. 同样需要 PROXY Protocol 解析(如果需要真实 IP)

4.8 UserServer — HTTP API

文件:server-code/UserServer/UserServer.h/.cpp

如果 UserServer 的 HTTP API 需要对外:

  1. 在 RustProxy 中添加 HTTP 或 TCP 代理规则
  2. HTTP 模式可基于域名路由,共享 8080 端口
  3. 无需 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_MSEC2100ms读取超时
T_WR_MSEC5100ms发送超时

经 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 代码无需修改

客户端连接流程不变:

  1. 连接 loginAddress:loginPort(现在是域名指向 RustProxy 公网地址)
  2. 登录成功后收到 stServerReturnLoginSuccessCmd.pstrIP:wdPort(这是 SERVERLIST 表中配置的域名和 RustProxy 代理端口)
  3. 连接 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.6
  • MSG_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.cn7RustProxy Server 1 公网 IP线路1
s2.gp.cn7RustProxy Server 2 公网 IP线路2
s3.gp.cn7RustProxy 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

十、启动顺序(改造后)

  1. 启动 RustProxy Server(公网机器)
  2. 启动 RustProxy Client(内网机器),连接公网隧道
  3. 通过 Web API 创建所有代理规则(或预先在 SQLite 中配好)
  4. 按原顺序启动游戏服务器集群:
    SuperServer → RecordServer → BillServer → MiniServer →
    SessionServer → ScenesServer → GatewayServer → FLServer
  5. 启动 ZtLoginServer、UserServer(如需要)
  6. 客户端配置 loginAddress 为域名,启动游戏

十一、风险与注意事项

11.1 延迟增加

原本直连变为:Client → RustProxy Server → TLS → RustProxy Client → GameServer

增加约 1-2 跳网络延迟 + TLS 加解密开销。对于 MMO 游戏影响可接受(心跳间隔 600 秒,非 FPS 级实时)。

11.2 带宽瓶颈

所有玩家流量经过 RustProxy Server 公网机器,需确保带宽充足。

并发玩家数人均带宽总带宽需求
10005 KB/s~40 Mbps
20005 KB/s~80 Mbps
50005 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 代理端口
2base/zSocket.h新增代码ProxyProtocolInfo 结构体 + parseProxyProtocol 声明
3base/zSocket.cpp新增代码parseProxyProtocol 实现(v1/v2 解析)
4GatewayServer/GatewayTask.h/.cpp修改代码verifyConn() 中调用 parseProxyProtocol,记录真实 IP
5FLServer/LoginTask.h/.cpp修改代码同上,LoginTask::verifyConn 解析真实 IP
6config.xml无需修改内网配置不变
7客户端 config.ini配置修改loginAddress → 多域名(如 s1.gp.cn:s2.gp.cn:s3.gp.cn
8DNS A 记录新增配置每个子域指向一台 RustProxy Server 公网 IP
9LoginServer/ZtLoginServer.h/.cpp可选修改如 ZtLoginServer 需对外,添加 PROXY Protocol 解析
10UserServer/UserServer.h/.cpp可选修改HTTP API 如需对外,配置 RustProxy HTTP 代理规则
11base/zSocket.h可选修改超时参数调整(仅在网络不稳定时)

实施

  • 核心代码修改仅 3 处zSocket 解析 + GatewayTask + LoginTask
  • 最关键的是数据库 SERVERLIST 表的域名配置
  • 域名 DNS 调度实现多 RustProxy Server 负载均衡与容灾,客户端零代码修改
  • 客户端仅改 config.ini 配置文件,天然支持域名解析