项目: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 运行时:
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --runtime aspnetcore --channel 7.0
发布包中附带 install-dotnet-runtime.sh、run-prod.sh,便于在宝塔里一键启动。
2.2 宝塔启动命令写法
错误示例: 把环境变量写在整条命令最前面,宝塔进程守护可能解析失败。
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 组时无法写入。
做法:
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 相关错误。
做法: 连接串增加:
SslMode=None;
三、服务端代码与配置
3.1 反向代理与 CORS
Nginx 终止 HTTPS 后,Kestrel 收到的是 HTTP 请求,需正确识别客户端 IP 与原始协议。
Program.cs 已配置:
UseForwardedHeaders():识别 X-Forwarded-For、X-Forwarded-Proto
- 生产环境启用 CORS,允许 WebGL 站点跨域带凭证访问
3.2 客户端网络配置
Assets/Resources/Network/NetworkSettings.json 生产段示例:
{
"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 鉴权流程
POST /api/auth/login → 返回 JWT token
- WebSocket 连接
/hub/game?access_token=...
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 是否正常
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):
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 { 前加入:
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/ 示例:
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 反代部分,不要动上述标记块。
保存后执行:
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 端口省略 :443(wss://server.vrast.cn/hub/game)
- 日志使用
UnityEngine.Debug.Log,避免与 DemonRoulette.Debug 命名空间冲突
6.3 成功标志
Console 应出现:
[GameHub] 连接 wss://server.vrast.cn/hub/game
[SignalR] 握手成功
随后进入在线大厅(房间列表)。
6.4 jslib 运行时错误:DemonRouletteWs is not defined
登录后 Hub 连接阶段,浏览器弹出 Unity 运行时错误,Console 完整堆栈类似:
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.jslib 在 mergeInto 外部声明了全局变量:
// ❌ 错误写法:打包后该变量不会进入 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:
// ✅ 正确写法: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");
如何确认已部署新包:
- 在 DevTools → Sources 或 Network 里打开
WebGL-Staging.framework.js(解压 .br 后)
- 搜索
DemonRouletteWs → 不应存在
- 搜索
drrWsState → 应存在
若仍能看到 DemonRouletteWs,说明上传的是旧 Build,或 Service Worker / IndexedDB 缓存了旧 framework.js(URL 带 ?v=legacy.1.1.1... 时尤其容易误判「已更新」)。
6.5 发布注意
- Release Build 重新打 WebGL 包并上传
mini.vrast.cn
- 强刷或清缓存:Service Worker 可能缓存旧包(项目内有
WebCacheRepair)
- DevTools → Application → Service Workers → Unregister(如有必要)
七、经验清单(Checklist)
服务端
Nginx
客户端
八、相关路径速查
| 用途 | 路径/地址 |
| 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 全链路:
- Nginx 需正确转发 WebSocket(Host、Upgrade、Forwarded-Proto、
/hub/ 独立规则)
- 服务端 在反代后需识别 HTTPS 与真实 IP
- WebGL 客户端 不能使用桌面端惯用的
ClientWebSocket + ConfigureAwait(false),必须走浏览器原生 WebSocket
排查顺序建议:HTTP 健康检查 → 登录 API → 浏览器 WS 测试 → Unity 包与缓存。当浏览器 new WebSocket(...) 已成功而 Unity 仍卡住时,应优先怀疑 WebGL 网络实现,而非继续改 Nginx。
归档日期:2026-06-06