背景 / Context
博主在尝试使用 Unity WebXR Export 导出项目为 WebXR App 时,遇到了 App 通信问题:原本利用 UDP/WebSocket 与外部 server 传输数据,但导出的 WebXR App 始终无法建立通信。
While exporting a Unity project to WebXR with Unity WebXR Export, I hit a networking wall: the app originally used UDP/WebSocket to talk to an external server, but the exported WebXR app could never establish the connection.
(Unity WebXR Export: https://github.com/De-Panther/unity-webxr-export 是一个很好的导出工具,如果你想把 Unity 项目转成 WebXR 应用。)
(Unity WebXR Export: https://github.com/De-Panther/unity-webxr-export is a solid tool if you want to ship your Unity project as a WebXR app.)
- 原本做法(简化示例)/ Original approach (simplified):
构建 WebGL 后,这些调用在浏览器里不会启动任何网络连接。using System.Net.Sockets; using System.Net.WebSockets; using System.Text; using System.Threading; // UDP 方式 udp = new UdpClient(); udp.Connect(remoteIP, remotePort); udp.Send(Encoding.UTF8.GetBytes(payload), payload.Length); // 或使用 ClientWebSocket ws = new ClientWebSocket(); await ws.ConnectAsync(new Uri(wssUrl), CancellationToken.None); await ws.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
After WebGL build, these UDP/ClientWebSocket calls never start a network connection in the browser. - 遇到的问题:Unity WebXR 导出后,HUD 显示“WS State: Connecting”,后端中继/UDP 无数据;UDP/ClientWebSocket 在 WebGL 下完全不工作。
Issue met: After Unity WebXR export, HUD stuck at “WS State: Connecting”, backend relay/UDP sees nothing; UDP/ClientWebSocket simply don’t work in WebGL. - 根因:Unity 官方文档说明 Web 平台不支持 .NET
System.Net套接字,也不支持原生 UDP/Ping/ClientWebSocket(参考 https://docs.unity3d.com/Manual/webgl-networking.html)。
Root cause: Web blocks .NETSystem.Netsockets; no native UDP/Ping/ClientWebSocket. - 结果:WebGL/WebXR 下原生 UDP/WS 代码会卡在 “Connecting” 或直接失败。
Result: Native UDP/WS in WebGL/WebXR stalls at “Connecting” or fails. - 关键限制 / Key limitation: 在 WebGL/WebXR 里无法使用 UDP;必须走浏览器的 WebSocket(ws/wss)。
UDP is not usable in WebGL/WebXR; you must use browser WebSocket (ws/wss).
方案 / Approach
-
使用浏览器 WebSocket API
- JS 插件
Assets/WebGLPlugins/WebSocketPlugin.jslib暴露WS_Connect/WS_Send/WS_Close,走浏览器 WS。
JS plugin exposesWS_Connect/WS_Send/WS_Closeto use browser WebSocket.mergeInto(LibraryManager.library, { WS_Socket: null, WS_Connect: function (urlPtr) { const url = UTF8ToString(urlPtr); Module.WS_Socket = new WebSocket(url); Module.WS_Socket.onopen = () => console.log("WS open", url); Module.WS_Socket.onerror = (e) => console.log("WS error", e); Module.WS_Socket.onclose = () => console.log("WS closed"); }, WS_Send: function (msgPtr) { if (!Module.WS_Socket || Module.WS_Socket.readyState !== WebSocket.OPEN) return; Module.WS_Socket.send(UTF8ToString(msgPtr)); }, WS_Close: function () { if (Module.WS_Socket) Module.WS_Socket.close(); }, }); - WebGL 端脚本调用插件进行通信(示例) / WebGL C# usage example:
using System.Runtime.InteropServices; using UnityEngine; public class WebGLWsSender : MonoBehaviour { [DllImport("__Internal")] private static extern void WS_Connect(string url); [DllImport("__Internal")] private static extern void WS_Send(string msg); [DllImport("__Internal")] private static extern void WS_Close(); public string wsUrl = "ws://localhost:8765"; public string testMessage = "hello from WebGL"; void Start() => WS_Connect(wsUrl); // 在 Start 中建立连接 / Connect in Start void Update() { if (Input.GetKeyDown(KeyCode.Space)) { WS_Send(testMessage); // 按空格发送测试消息 / Send on space key } } void OnApplicationQuit() => WS_Close(); // 退出时关闭 / Close on quit }
- JS 插件
-
WSS 与证书 / WSS & Certificates
- 页面是 HTTPS 时必须用 WSS(浏览器拦截 ws://)。
If the page is HTTPS, WSS is required (ws:// is blocked). - 自签示例 / Self-signed example:
openssl req -newkey rsa:2048 -nodes -keyout selfsigned.key -x509 -days 365 -out selfsigned.crt python3 wss_udp_relay.py --ws-host 0.0.0.0 --ws-port 8765 --udp-host 127.0.0.1 --udp-port 9000 --use-ssl --certfile selfsigned.crt --keyfile selfsigned.key - 关键:浏览器需先访问
https://<relay-ip>:<port>接受自签证书,之后wss://<relay-ip>:<port>才能握手(Quest/PC 浏览器同理)。
Important: You must visithttps://<relay-ip>:<port>once to accept the self-signed cert, thenwss://<relay-ip>:<port>will succeed. - 生产建议用可信 CA 证书。
For production, use a trusted CA cert.
- 页面是 HTTPS 时必须用 WSS(浏览器拦截 ws://)。
使用步骤 / How to Use
WebXR 端 / WebXR side
- 放置 JS 插件
Assets/WebGLPlugins/WebSocketPlugin.jslib。
Place the JS plugin underAssets/WebGLPlugins/WebSocketPlugin.jslib. - 使用一个 WebGL 脚本调用
WS_Connect/WS_Send/WS_Close,设置wsUrl(ws://或wss://)。
Use a WebGL C# script callingWS_Connect/WS_Send/WS_Close, setwsUrl(ws://orwss://). - 构建 WebGL;如是 HTTPS,按上文处理证书并在浏览器信任。
Build WebGL; if HTTPS, handle certs and trust in the browser. - 数据流:WebGL → WS/WSS → /WS 接收端。
Flow: WebGL → WS/WSS → WS receiver.
快速检查 / Quick checklist
wsUrl协议/主机/端口是否正确,HTTPS 场景是否用 WSS。
wsUrlscheme/host/port correct; WSS required if page is HTTPS.- 自签证书是否已在浏览器手动信任。
Self-signed cert trusted by visitinghttps://<relay-ip>:<port>. - Relay 日志是否有 “client connected”/转发字节。
Relay logs show “client connected” and forwarded bytes. - 接收端端口是否正确监听(UDP 或 WS)。
Receiver listening on the right port (UDP or WS).