项目:NewRingGame(Unity WebGL + .NET 7 API)
环境:CentOS 7、宝塔面板、同机部署
WebGL:https://mini.vrast.cn/
API:https://server.vrast.cn
部署目录:/www/wwwroot/ringgame

一、背景

《恶魔轮盘》联机版采用 Unity WebGL 客户端 + .NET 7 服务端 架构。首次上生产时,WebGL 与 API 部署在同一台 CentOS 7 服务器,通过宝塔面板管理 Nginx、进程与 FTP。

联调过程中,HTTP 登录接口已经可用,但游戏界面长期停在 「连接 Hub…」,无法进入在线大厅。本文记录从部署到联机打通的完整排查过程与最终解法。

二、部署阶段:服务器与环境

2.1 .NET 7 运行时(CentOS 7)

CentOS 7 官方源没有 .NET 7 包,不能照搬 Ubuntu 的 apt install

做法: 使用微软安装脚本安装 ASP.NET Core 7 运行时:

1
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --runtime aspnetcore --channel 7.0

发布包中附带 install-dotnet-runtime.shrun-prod.sh,便于在宝塔里一键启动。

2.2 宝塔启动命令写法

错误示例: 把环境变量写在整条命令最前面,宝塔进程守护可能解析失败。

1
ASPNETCORE_ENVIRONMENT=Production nohup dotnet ...

推荐:

  • 宝塔「启动文件」填:/www/wwwroot/ringgame/run-prod.sh
  • 或 systemd:ExecStart=/usr/bin/dotnet /www/wwwroot/ringgame/NewRingGame.Server.dll
  • 环境变量在 run-prod.sh 或 systemd Environment= 中设置

2.3 FTP 上传 553 Permission denied

宝塔站点目录属主多为 www:www,权限 drwxr-xr-x,FTP 用户不在 www 组时无法写入。

做法:

1
2
3
4
chmod 775 /www/wwwroot/ringgame
chown -R www:www /www/wwwroot/ringgame
# 将 FTP 用户加入 www 组
usermod -aG www <ftp用户名>

GM 面板发布默认 FTP 主机 server.vrast.cn,远端目录 /www/wwwroot/ringgame

2.4 MySQL SSL 握手失败

宝塔本地 MySQL 连接时,若未配置 SSL,.NET 驱动可能报 SSL 相关错误。

做法: 连接串增加:

1
SslMode=None;

三、服务端代码与配置

3.1 反向代理与 CORS

Nginx 终止 HTTPS 后,Kestrel 收到的是 HTTP 请求,需正确识别客户端 IP 与原始协议。

Program.cs 已配置:

  • UseForwardedHeaders():识别 X-Forwarded-ForX-Forwarded-Proto
  • 生产环境启用 CORS,允许 WebGL 站点跨域带凭证访问

3.2 客户端网络配置

Assets/Resources/Network/NetworkSettings.json 生产段示例:

1
2
3
4
5
6
7
{
"release": {
"host": "server.vrast.cn",
"port": 443,
"useTls": true
}
}

派生地址:

  • API:https://server.vrast.cn/api
  • Hub:wss://server.vrast.cn/hub/game?access_token={token}

3.3 登录与 Hub 鉴权流程

  1. POST /api/auth/login → 返回 JWT token
  2. WebSocket 连接 /hub/game?access_token=...
  3. GameHub.OnConnectedAsync 校验 token,失败则 Context.Abort()

因此:登录 200 只说明 HTTP 正常;卡在「连接 Hub…」说明 WebSocket 阶段有问题。

四、联机故障:「连接 Hub…」

4.1 现象

项目 状态
GET /health 200
POST /api/auth/login 200,有 token
CORS 预检 正常,Access-Control-Allow-Origin: https://mini.vrast.cn
游戏界面 停在「连接 Hub…」
DevTools → Network → Socket 无连接,或 Unity 侧无后续日志

Console 可见 [GameHub] 连接 wss://server.vrast.cn/hub/game,但无「握手成功」或明确报错。

4.2 分层排查

第一层:服务端 HTTP 是否正常

1
2
3
4
curl -s https://server.vrast.cn/health
curl -s -X POST https://server.vrast.cn/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"secret123"}'

第二层:浏览器原生 WebSocket 是否正常

https://mini.vrast.cn/ 控制台执行(替换真实 token):

1
2
3
4
const ws = new WebSocket("wss://server.vrast.cn/hub/game?access_token=你的token");
ws.onopen = () => console.log("WS 成功");
ws.onerror = (e) => console.log("WS 失败", e);
ws.onclose = (e) => console.log("WS 关闭", e.code);

若输出 「WS 成功」,则 Nginx 反代与 Hub 均已正常,问题在 Unity WebGL 客户端

第三层:Unity 客户端

WebGL 上 System.Net.WebSockets.ClientWebSocket 不可靠;配合 ConfigureAwait(false)Task.Run 会导致异步回调无法继续,界面永久停在「连接 Hub…」。

五、Nginx 配置(宝塔)

5.1 全局:WebSocket Upgrade 映射

宝塔 → 软件商店 → Nginx → 配置修改,在 http { 内、server { 前加入:

1
2
3
4
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

5.2 站点:server.vrast.cn

原配置主要问题:

问题 原值 修正
Host 头 127.0.0.1:$server_port $host
转发协议 缺失 X-Forwarded-Proto $scheme
Connection 固定 "upgrade" $connection_upgrade
Hub 路径 与普通 API 混用 单独 location /hub/
发送超时 30s 86400s(长连接)

/hub/ 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
location /hub/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
}

5.3 宝塔保存配置的坑

直接删改 SSL / 错误页注释块会导致 「配置文件保存失败」。必须保留:

  • #SSL-START 内的 #error_page 404/404.html;
  • #ERROR-PAGE-START#ERROR-PAGE-END 整段
  • #CERT-APPLY-CHECK--START/END
  • #HTTP_TO_HTTPS_START/END

只改 location 反代部分,不要动上述标记块。

保存后执行:

1
nginx -t && nginx -s reload

六、WebGL 客户端修复

6.1 问题根因

问题 说明
ConfigureAwait(false) WebGL 无线程池,continuation 无法执行
Task.Run WebGL 不支持,接收循环无法启动
ClientWebSocket 浏览器测试 WS 成功,Unity 侧仍挂起

6.2 解决方案

WebGL 改用 浏览器原生 WebSocket(jslib):

文件 作用
Assets/Plugins/WebGL/BrowserWebSocket.jslib JS 层 WebSocket 封装
Assets/Scripts/Network/BrowserWebSocketTransport.cs C# 桥接
Assets/Scripts/Network/SignalRWebSocketClient.cs WebGL 自动选择传输层

其他改动:

  • GameHubClient:20 秒连接超时
  • NetworkConfig:443 端口省略 :443wss://server.vrast.cn/hub/game
  • 日志使用 UnityEngine.Debug.Log,避免与 DemonRoulette.Debug 命名空间冲突

6.3 成功标志

Console 应出现:

1
2
[GameHub] 连接 wss://server.vrast.cn/hub/game
[SignalR] 握手成功

随后进入在线大厅(房间列表)。

6.4 jslib 运行时错误:DemonRouletteWs is not defined

登录后 Hub 连接阶段,浏览器弹出 Unity 运行时错误,Console 完整堆栈类似:

1
2
3
4
5
Uncaught ReferenceError: DemonRouletteWs is not defined
at _DemonRoulette_WsCreate (WebGL-Staging.framework.js.br:10:26428)
at WebGL-Staging.wasm.br:0x15d59f
at invoke_ii (WebGL-Staging.framework.js.br:10:394006)
...

原因: 第一版 BrowserWebSocket.jslibmergeInto 外部声明了全局变量:

1
2
3
4
5
6
7
8
9
// ❌ 错误写法:打包后该变量不会进入 framework.js 作用域
var DemonRouletteWs = { nextId: 1, sockets: {} };

mergeInto(LibraryManager.library, {
DemonRoulette_WsCreate: function (urlPtr) {
var id = DemonRouletteWs.nextId++; // 运行时报 ReferenceError
...
}
});

Unity / Emscripten 只会把 mergeInto(LibraryManager.library, ...) 内的符号打进 framework.js。外部 var 在 WebGL 运行时不可见,C# 通过 [DllImport("__Internal")] 调用 _DemonRoulette_WsCreate 时就会崩溃。

正确写法: 使用 Emscripten 的 $ 依赖语法保存状态,并 autoAddDeps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ✅ 正确写法:Assets/Plugins/WebGL/BrowserWebSocket.jslib
var DemonRouletteBrowserWsPlugin = {
$drrWsState: { nextId: 1, sockets: {} },

DemonRoulette_WsCreate__deps: ["$drrWsState"],
DemonRoulette_WsCreate: function (urlPtr) {
var id = drrWsState.nextId++;
drrWsState.sockets[id] = entry;
...
},
// 其余函数同样声明 __deps: ["$drrWsState"]
};

mergeInto(LibraryManager.library, DemonRouletteBrowserWsPlugin);
autoAddDeps(DemonRouletteBrowserWsPlugin, "$drrWsState");

如何确认已部署新包:

  1. 在 DevTools → Sources 或 Network 里打开 WebGL-Staging.framework.js(解压 .br 后)
  2. 搜索 DemonRouletteWs不应存在
  3. 搜索 drrWsState应存在

若仍能看到 DemonRouletteWs,说明上传的是旧 Build,或 Service Worker / IndexedDB 缓存了旧 framework.js(URL 带 ?v=legacy.1.1.1... 时尤其容易误判「已更新」)。

6.5 发布注意

  1. Release Build 重新打 WebGL 包并上传 mini.vrast.cn
  2. 强刷或清缓存:Service Worker 可能缓存旧包(项目内有 WebCacheRepair
  3. DevTools → Application → Service Workers → Unregister(如有必要)

七、经验清单(Checklist)

服务端

  • API 监听 127.0.0.1:8080,Nginx 反代到 443
  • curl /healthcurl login 均正常
  • MySQL 连接串含 SslMode=None(宝塔本地库)
  • ForwardedHeaders、CORS 已启用

Nginx

  • 全局 map $http_upgrade $connection_upgrade
  • Host$host,非 127.0.0.1
  • X-Forwarded-Proto $scheme
  • /hub/ 单独 location,proxy_buffering off
  • 宝塔 SSL/ERROR-PAGE 注释块未删

客户端

  • NetworkSettings.json release 段指向生产域名
  • WebGL 使用 BrowserWebSocket.jslib(非纯 ClientWebSocket)
  • 浏览器原生 WS 测试通过后再查 Unity 包版本
  • 上传后清 SW / 站点缓存

八、相关路径速查

用途 路径/地址
WebGL 站点 https://mini.vrast.cn/
API / Hub https://server.vrast.cn
服务端部署 /www/wwwroot/ringgame
GM 发布面板 server/scripts/dev-manager/app.py
网络配置 Assets/Resources/Network/NetworkSettings.json
Hub 服务端 server/src/NewRingGame.Server/Hubs/GameHub.cs
登录 UI Assets/Scripts/UI/OnlineNetworkUiBinder.cs

九、小结

本次联机打通,问题并不在「登录接口」,而在 Hub WebSocket 全链路

  1. Nginx 需正确转发 WebSocket(Host、Upgrade、Forwarded-Proto、/hub/ 独立规则)
  2. 服务端 在反代后需识别 HTTPS 与真实 IP
  3. WebGL 客户端 不能使用桌面端惯用的 ClientWebSocket + ConfigureAwait(false),必须走浏览器原生 WebSocket

排查顺序建议:HTTP 健康检查 → 登录 API → 浏览器 WS 测试 → Unity 包与缓存。当浏览器 new WebSocket(...) 已成功而 Unity 仍卡住时,应优先怀疑 WebGL 网络实现,而非继续改 Nginx。

归档日期:2026-06-06