news 2026/9/5 17:48:14

Unity WebGL为何不能用Socket?WebSocket联机方案与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity WebGL为何不能用Socket?WebSocket联机方案与实战

上周帮一个朋友排查 WebGL 联调问题,他把一整套基于 TcpClient 的通信代码从 PC 端直接搬进了 WebGL 构建包,在 Editor 里跑得风生水起,发布到浏览器就彻底完蛋:服务器看不到连接进来,游戏里也不报错,像一拳打在棉花上。这种问题我见过太多次了——Unity WebGL 的网络现状和你熟悉的 Windows/Android 完全是两套逻辑。浏览器禁掉原生 Socket 不是因为什么策略限制,而是浏览器这个环境本身就没有给网页开放裸 TCP 的能力。那 WebGL 项目到底该怎么连服务器?为什么最终几乎都绕不开 WebSocket?从代码层面怎么接才稳?这篇文章就把整条链路讲透。

先说清楚适用人群:如果你正准备做 Unity WebGL 多人在线、网页版互动小游戏、或者要把现有的 Socket 通信项目迁到浏览器端,这篇内容能帮你少走至少一周弯路。看这篇文章需要你已经有 Unity 基础,知道 MonoBehaviour、协程、以及基本的网络概念,但不需要你是网络协议专家。我会从“为什么不能用 Socket”讲到“WebSocket 怎么落地”,再给出一套可以直接抄作业的连接管理代码,最后把项目里踩过的坑按“排查手册”的方式整理给你。

1. 浏览器没有 Socket,这句话到底在说什么

1.1 一次真实翻车:Editor 正常、WebGL 静默失败

朋友那套翻车代码结构大概是这样的:启动后 new 一个 TcpClient,Connect 到服务器的 8000 端口,然后在独立线程里开一个 NetworkStream 循环读取数据。Windows 本地测试、Android 真机测试都没问题,结果一打包 WebGL 就不通。

这里有一个很迷惑的点:把 Unity WebGL 包挂到服务器上,打开浏览器,你几乎看不到任何报错。如果你用的还是较老的 TcpClient 代码,运气好一点会看到一个未捕获的异常,运气不好直接静默失败。原因是 WebGL 的代码执行环境不是操作系统,而是浏览器的 JavaScript 沙箱加 IL2CPP 编译产物。System.Net.Sockets 这套命名空间里的类型在编译期不会消失,但底层依赖的 Berkeley Socket 接口在浏览器里根本不存在,运行时一碰就废。

很多团队遇到这个问题时第一反应是去搜“Unity WebGL Socket”,然后看到铺天盖地的 WebSocket 方案,一开始会误以为这是 Socket 的“替代品”。但理解越往后越会发现,这里本质不是技术替代,而是架构切换。你原来的 TCP 通信层、收发线程、粘包处理、断线重连,全部要在 WebGL 上换一种姿势重新实现一遍。

1.2 浏览器网络模型和操作系统的关键差异

为什么浏览器不给网页开放原生 Socket?站在安全设计角度很好理解:如果任何一个网页都能直接往你机器的任意端口发 TCP 包,那网页就可以绕过 HTTP 协议去扫描内网、探测服务、甚至伪装成你的本机进程做任何事。浏览器作为中间层,对内是隔离沙箱,对外则把网络能力收窄成几张“白名单 API”。

所以如果你把“浏览器禁掉原生 Socket”翻译成更准确的话,应该是:浏览器从来就没有给 Web 页面开放过 TCP/UDP Socket 的底层能力。不是以前有、后来禁了,而是整个架构里就不存在这个通道。网页能联网的方式从一开始就限定了 HTTP 请求,以及后来陆续补充的 WebSocket、WebRTC、SSE 等协议。这些协议全都要走浏览器封装好的 API。

Unity WebGL 构建出来的应用本质上是一个跑在浏览器里的网页应用。你写的 C# 代码会被 IL2CPP 转成 C++,再编译成 WebAssembly,在浏览器的沙箱里运行。IL2CPP 层可能有 Socket 相关的声明,但那只是“看起来存在”,真正执行时如果要用原生 Socket 的能力,必须由浏览器提供,而这个能力浏览器不愿意给。于是 Unity 给你留了一条官方建议的路:用 WebSocket。

1.3 误把 Socket 当兄弟:二者根本不在一层

Socket 是一个操作系统层面的网络编程接口,它管的是 IP 地址和端口之间的数据传输。WebSocket 则是一个应用层协议,它靠 HTTP Upgrade 机制建立一条长连接,之后客户端和服务端可以在同一条连接上双向推送数据,底层封装了 TCP。你在浏览器里用的是 WebSocket,但浏览器内部真正干活的时候,仍然是帮你在操作系统层面建立了一条 TCP 连接。

所以更准确的理解是:原生 Socket 是你直接在操作系统上开一条数据管道,想怎么玩怎么玩;WebSocket 是浏览器给你的一根“经过安检的数据管道”,通道仍然是 TCP,只不过收发数据要用它规定的格式来。

两者对比如下:

对比项原生 Socket (TcpClient)WebSocket
可用平台Windows / macOS / Linux / Android / iOS浏览器(WebGL)、微信小游戏等
底层通道操作系统直接提供的 TCP 连接浏览器内部封装好的 TCP 长连接
是否需要握手不需要,直接 Connect先发 HTTP Upgrade 握手再切协议
安全限制受系统防火墙约束受浏览器跨域、混合内容等约束
数据格式裸字节流,自己处理分包粘包文本帧或二进制帧,有帧边界
断线行为由操作系统和网络栈决定由浏览器生命周期和网络状态决定

2. 选型判断:为什么 WebSocket 是最合适的默认答案

2.1 HTTP 轮询、长轮询和 SSE,为什么都不够用

既然浏览器能发 HTTP 请求,那能不能不去碰 WebSocket,直接用 HTTP 轮询?可以,很多早期的网页游戏就是这么干的。前端每隔 1 秒发一个 Ajax 请求问服务器“有没有新消息”,服务器返回最新状态。但实时性越强,这个方案的弱点越明显:1 秒的轮询间隔在多人对战场景下延迟太大,缩短到 100 毫秒又会把服务器打爆。

长轮询稍微好一点:客户端发一个请求,服务器先挂着不回复,等有新消息才返回,客户端收到后再立刻发起下一个请求。但每次请求都要重新建立 HTTP 连接,服务器资源开销不小,而且消息到达的时机会被请求到达的时机影响,协议上还是有点拧巴。SSE(Server-Sent Events)是单向的,只能服务器往客户端推,客户端要给服务器发数据还得另外发 HTTP 请求,天然不适合游戏这种双向高频交互。

这些方案最大的问题在于:它们全是围绕 HTTP“请求-响应”模型设计的修正方案。而游戏服务端需要的是一个真正能双向推送的长连接通道。WebSocket 的握手依然走 HTTP,但成功之后连接就“升级”成双向消息通道,语义上最接近原生 TCP 长连接。

2.2 WebSocket 为什么是目前最均衡的答案

我们把几个关键需求列出来看:实时性要够,延迟尽量低;双向通信,双方可以随时发数据;连接数不能太消耗服务器;开发成本可控,周边库要成熟;跨平台兼容性要好。横向对比下来,WebSocket 是当前浏览器环境下满足所有条件的最优解。

先说延迟。WebSocket 握手完成后就是一个常驻连接,消息不需要像 HTTP 那样每发一次就重新建连,头部开销比 HTTP 小很多,数据报文直接就能发。对大多数实时类游戏(策略同步、房间交互、棋牌、休闲竞技)来说,WebSocket 的延迟完全在可接受范围内。

再说兼容性。WebSocket 已经在所有主流浏览器里原生支持了很多年,Unity WebGL 构建包里也可以通过第三方库封装成类似原生 Socket 的使用体验。你在 C# 里写出来,代码长得像异步网络调用,但底层在浏览器里用的是原生 WebSocket API,没有额外的插件安装问题。

开发成本这里多说一句:Unity 官方没有提供一套“WebGL 专用网络库”,但社区有非常成熟的 NativeWebSocket 之类的库,封装好了浏览器 WebSocket 与 C# 事件之间的桥接,把 Open/Message/Close/Error 暴露成事件。我们要做的就是在 C# 层封装一套自己的连接管理逻辑,而后端只要是支持 WebSocket 的服务端框架都行。

2.3 WebTransport、WebRTC DataChannel 要不要等

这两年 WebTransport 和 WebRTC DataChannel 的话题越来越热。WebTransport 可以基于 QUIC 提供更接近原生 Socket 的体验,支持单向流、双向流以及数据报;WebRTC DataChannel 则能在浏览器之间建立 P2P 连接。看起来都很香,但客观评估一下:WebTransport 虽然已经在部分浏览器里可用,但它的生态成熟度和服务器端框架支持还远不如 WebSocket,对 Unity WebGL 的库支持更是稀缺。

WebRTC DataChannel 适合端到端直连,但绝大多数游戏架构是客户端连中心服务器,而不是浏览器直接互连。而且 WebRTC 需要信令服务器做协商,复杂度高不少。

所以我的建议是:2025 年做 Unity WebGL 联机项目,默认选 WebSocket,不要犹豫。等哪天 Unity WebGL 的 WebTransport 接入库成熟了,或者你的需求场景有明显低延迟要求再考虑升级。技术选型最怕的不是选错“完美方案”,而是为了博一个不确定的未来,把当下项目拖进复杂维护的泥潭。

3. 实战落地:给 WebGL 工程接入一个通用 WebSocket 连接管理器

3.1 选库与工程配置:别自己从零写 jslib

Unity WebGL 要用 WebSocket,正统路径有两条:自己写 jslib 桥接浏览器的 WebSocket API,或者用社区封装好的 C# 库。自己写 jslib 不是不行,但要把 OpenMessageCloseError 事件通过 SendMessage 传回 C#,还要处理二进制数据的传输和内存释放,工作量不小且非常容易踩到内存管理问题。如果团队里没有人对 Unity 与 JavaScript 互操作有丰富经验,我强烈建议直接引入社区库。

目前 Unity 社区用得比较多的是 NativeWebSocket,操作方式很像你在前端里用 WebSocket 的感受。这个库同时支持 WebGL 与原生平台,也就是说你在 Windows/macOS Editor 里也能用同一套 WebSocket API 调试,问题少很多。它是提供 NuGet 以外最常见的下载方式是 GitHub 下载源码包导入 Unity。导入后确认一下插件目录里有 WebGL 平台对应的 jslib 文件,并且在 Plugin Inspector 里勾选了 WebGL 平台,否则打包时会报找不到 API 的错误。

工程配置上还有一点容易被忽略:如果你同时引用多个网络库,注意命名空间冲突。比如 NativeWebSocket 里定义了一个 WebSocket 类,websocket-sharp 里也有一个 WebSocket 类,两个库一起用可能在编译期让编译器直接懵掉。这种问题不是技术难点,但第一次遇到确实很费排查时间。我的经验是项目里只保留一个 WebSocket 库作为底层实现,再自己包一层业务专用的连接接口,外部代码完全不感知具体是哪个库。

3.2 先搭一个不用改业务的连接抽象

工程结构比较小的时候,直接在需要发消息的 MonoBehaviour 里 new 一个 WebSocket 就行。但到项目后期,一会要接心跳、一会要处理断线重连、一会要兼容多个游戏场景,没有抽象层会导致同样的连接逻辑散落得到处都是。建议第一步就定义一个轻量接口:

public interface ITransport { event Action<byte[]> OnPayload; void Open(string uri); void Send(byte[] payload); void Close(); }

这个接口不关心底层走的是 WebSocket 还是 TCP,只关心三件事:打开连接、发数据、收数据。业务层不依赖具体的连接过程,只处理收到的字节数组。在 WebGL 平台上,实际实现类内部封装 NativeWebSocket;在原生平台上,如果以后你想切回 TCP,只要再写一个 TcpTransport 实现同一接口,业务代码一行不用改。

如果你一开始觉得这个抽象没必要,可以先在 WebSocketClient 类里把逻辑写扎实,等第二个需要网络的系统出现再抽接口。但至少有一点要记住:不要在 Update 里直接 new WebSocket,也不要在每个 UI 面板里各写一套连接逻辑。让网络连接只有一个实例、一条生命周期,否则光断线重连和消息路由就能让你改到怀疑人生。

3.3 核心代码:连接、收消息、心跳、断线重连

下面这个类是我实际项目里用过的一个精简版本,代码基于 NativeWebSocket 的 API 风格编写,不同版本的回调签名可能略有差异,你用的时候对着包里的接口声明调整一下即可。

using System; using System.Collections; using System.Collections.Concurrent; using System.Text; using System.Threading.Tasks; using UnityEngine; using NativeWebSocket; public class WebSocketClient : MonoBehaviour { private WebSocket socket; private bool userClosed; private ConcurrentQueue<byte[]> messageQueue = new ConcurrentQueue<byte[]>(); // 业务层订阅这个事件,只在 Unity 主线程里触发 public event Action<byte[]> OnPayloadReceived; public async Task ConnectAsync(string url) { userClosed = false; if (socket != null) { socket.OnOpen -= HandleOpen; socket.OnMessage -= HandleMessage; socket.OnClose -= HandleClose; socket.OnError -= HandleError; socket = null; } socket = new WebSocket(url); socket.OnOpen += HandleOpen; socket.OnMessage += HandleMessage; socket.OnClose += HandleClose; socket.OnError += HandleError; await socket.Connect(); } public void SendPayload(byte[] payload) { if (socket != null && socket.State == WebSocketState.Open) { socket.Send(payload).ContinueWith(t => { if (t.IsFaulted) { Debug.LogError($"[WebSocket] send failed: {t.Exception}"); } }); } } public void CloseConnection() { userClosed = true; socket?.Close(); } private void HandleOpen() { Debug.Log("[WebSocket] connected"); StartCoroutine(HeartbeatLoop()); } private void HandleMessage(byte[] bytes) { // 收到消息不做业务逻辑,先入队列,保证在 Unity 主线程统一分发 messageQueue.Enqueue(bytes); } private void HandleClose(WebSocketCloseCode closeCode) { Debug.Log($"[WebSocket] closed, code: {closeCode}"); if (!userClosed) { StartCoroutine(ReconnectLater(3f)); } } private void HandleError(string errorMsg) { Debug.LogError($"[WebSocket] error: {errorMsg}"); } private void Update() { while (messageQueue.TryDequeue(out byte[] payload)) { OnPayloadReceived?.Invoke(payload); } } private IEnumerator HeartbeatLoop() { var wait = new WaitForSeconds(10f); while (socket != null && socket.State == WebSocketState.Open) { yield return wait; if (socket != null && socket.State == WebSocketState.Open) { // 业务心跳包:让中间层/服务器及时感知连接存活 byte[] ping = Encoding.UTF8.GetBytes("{\"type\":\"ping\"}"); SendPayload(ping); } } } private IEnumerator ReconnectLater(float delay) { yield return new WaitForSeconds(delay); if (!userClosed) { // 重新连接时尽量带上重连次数,退避策略可以后续优化 await ConnectAsync(socket.Url); } } }

有几个点我特别想提醒:

心跳不能省。很多 WebGL 项目连上之后只是偶尔发数据,看起来没问题,但实际上浏览器、Nginx 或云厂商的负载均衡器可能会在一段时间没有数据传输后悄悄断开空闲连接。应用层心跳相当于定期告诉网络路径上的所有中间设备“这个连接还活着”,能有效降低莫名其妙掉线的概率。心跳间隔一般 10 到 30 秒都可以,我习惯放在 10 到 15 秒,太频繁会浪费流量,太慢则容易触发服务器空闲断开阈值。

断线重连必须做退避和次数限制。如果服务器临时重启,客户端每 3 秒重连一次,可能把刚启动的服务器连接池打满。更合理的是指数退避+最大次数:比如第一次 1 秒、第二次 2 秒、第三次 4 秒,最多尝试 5 此后停止,等用户主动点击或页面重新可见时再连。这个策略我后面会细说。

3.4 关于 WSS 和混合内容:这一步经常被忽略

WebSocket 连接地址有ws://wss://两种。如果你的网页是 HTTP 协议,可以用ws://;但如果你的站点上了 HTTPS(现在这是默认配置),浏览器会强制要求 WebSocket 也使用wss://,否则直接拦截。这是因为 HTTPS 页面里加载不安全连接属于混合内容,浏览器安全策略不允许。

这个报错在控制台里长这样:

Mixed Content: The page at 'https://game.example.com' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://game-srv.example.com:8080/ws'. This request has been blocked.

很多项目在开发环境用 http+ws 联调没问题,一部署到正式环境就发现所有连接建立不起来,十有八九就是这个原因。解决方案很简单:把连接地址从ws://换成wss://,前提是服务器配置了有效的 SSL 证书。

有一点要特别提醒做 WebGL 的团队:WebGL 端几乎无法使用自签名证书的 wss 地址。因为浏览器不会像原生 App 那样信任你手动导入的系统证书,自签名证书在浏览器里会被当成不安全连接拒绝。所以在测试环境如果也要避免混合内容问题,一个相对省事的办法是让开发环境也走 HTTPS,或者直接把前端项目放在 localhost 下测试,因为 localhost 通常被浏览器视为安全上下文。

4. 我踩过的坑:WebGL 连接问题排查手册

4.1 高频问题速查表

下面这张表是我在不同项目里被反复问到的问题汇总,几乎每个 WebGL 联调团队都会遇到其中几个:

现象可能原因处理方式
浏览器控制台提示 Mixed Content页面是 HTTPS,连接地址是 ws://换用 wss://,检查服务器证书
连接成功后马上断开服务器没配置 WebSocket Upgrade 支持检查网关/代理是否转发 Upgrade 头
Editor 里正常,WebGL 构建后连不上底层网络 API 差异确认代码在 WebGL 平台走的是 WebSocket 而非 TcpClient
收到消息后界面卡顿在消息回调里直接处理大量 Unity API消息先入队列,在 Update 中统一分发
打开多个页面,一个连接顶掉另一个服务端按账号或设备踢线看需求设计顶号逻辑或允许同账号多开
手机切后台再回来,连接断了浏览器节能策略挂起页面监听页面可见性,切回前台时主动重连
Chrome 提示不支持 WebGL2显卡驱动或硬件加速被关闭开启浏览器硬件加速、更新显卡驱动
消息偶尔丢失或顺序错乱应用层没有处理分包、消息边界在帧协议里加入长度字段与序号

表中最后那条“消息丢失或顺序错乱”值得展开讲。WebSocket 底层保证消息在一条 TCP 连接上的有序传输,但如果你一次发送超过浏览器缓冲阈值的大数据,业务方若不自己处理分包,就可能出现一条逻辑消息被拆到多个 WebSocket 消息里的情况。更常见的场景是服务端把多条小消息连续发过来,如果你只按“收到一条就当一条完整业务消息”处理,就会把两条消息拼坏。自己的业务协议里必须定义消息边界,一般做法是包头固定长度,前 4 字节存消息总长,后面再跟实际内容。这个问题与用不用 TCP 无关,WebSocket 只是帮你解决了 TCP 字节流上的封包,但应用层的消息边界还是得你自己维护。

4.2 为什么不要在消息回调里直接操作游戏场景

NativeWebSocket 在 WebGL 平台上的回调最终来自浏览器的事件循环,在 Unity 里的表现会和主线程有千丝万缕的关系。如果直接在 OnMessage 里执行 Instantiate、修改 UI 文本、给角色赋值,短消息时看着没问题,一旦服务器同时推来几十条消息,就会在同一个回调栈里密集调用 Unity API,轻则掉帧,重则出现一些奇奇怪怪的时序问题。

建议把消息处理设计成“生产者-消费者”模式:网络回调只负责把原始字节入队,每帧 Update 从队列里取出数据再分发到业务系统。这样好处有两个:一是网络回调与 Unity 主线程的执行时机解耦,二是你可以控制每帧处理多少条消息,防止一帧被大量消息卡死。

这种模式不仅适用于 WebGL,在原生平台处理高速 TCP 消息时同样有效。如果你以后扩展成 PC、Android、iOS 多端共用一套客户端逻辑,用这个模式能省很多事。

4.3 页面切后台、浏览器休眠与断线重连

WebGL 项目有个独特的生命周期问题:用户在手机浏览器里玩到一半切到微信回个消息,再切回游戏,可能发现服务器已经断开连接了。这是因为移动端浏览器为了省电,会在页面进入后台时冻结 JavaScript 的执行,socket 相关回调全部暂停,网络连接很容易被系统或服务器超时回收。

解决这个问题的关键不是单纯在 C# 里加“重连”按钮,而是要让游戏能感知页面可见性变化。Unity 模板生成的 index.html 是可以改的,在 HTML 模板的脚本里用 visibilitychange 事件监听页面状态,再调用 Unity 的 SendMessage 把状态告诉 C#:

<script> document.addEventListener('visibilitychange', function() { var state = document.hidden ? 'hidden' : 'visible'; try { gameInstance.SendMessage('NetworkManager', 'OnPageVisibilityChanged', state); } catch (e) {} }); </script>

然后在 C# 脚本里写一个对应方法:

public void OnPageVisibilityChanged(string state) { // hidden:可以暂停心跳,但不要太快主动断开 // visible:如果连接已经断开,尝试重连 if (state == "visible" && (socket == null || socket.State != WebSocketState.Open)) { StartCoroutine(ReconnectLater(0.5f)); } }

这里有个细节:不要一进入后台就立刻调用 Close,因为很多场景切后台时间很短,快速切回来没必要重新握手。比较稳妥的做法是后台时只是暂停发心跳,切回前台时检查连接状态,没断就继续用,断了再重连。

4.4 Editor 与 WebGL 必须共用同一套连接方案

不少人为了联调方便,在 Windows 编辑器里用 TcpClient,打包到 WebGL 才切到 WebSocket,理由是“编辑器跑 WebSocket 还得开个 WS 服务器,太麻烦了”。这个做法前期很快,后期会让你欲仙欲死。原因很简单:两套协议栈的数据格式、错误处理方式、连接生命周期都不同,你很难保证在编辑器里调通的逻辑到了 WebGL 里还能原样跑通。

我在项目里的习惯是:编辑器里也默认走 WebSocket。NativeWebSocket 这类库在原生平台上同样有实现,它能在 Windows/macOS Editor 里直接连接 WebSocket 服务端,底层行为与 WebGL 构建版接近。这样我在写逻辑时脑子里只有一套连接模式,所有调试结论到 WebGL 都是可复现的。如果一定要在编辑器里测试原生的低延迟 TCP 通道,那就通过配置开关显式切换,至少要保证 CI 或者手工测试清单里包含“WebGL 构建包联调”这一个固定步骤。

5. 不止 WebGL:一套抽象打通多平台 Socket/WebSocket

5.1 连接层接口与平台工厂

很多团队做完 WebGL 之后,产品又要求出 Android 版、iOS 版,甚至 PC 客户端。这时候如果你在 WebGL 版本里把代码写死在 WebSocket 上,迁移起来就会很别扭。反过来,如果一开始就设计了 ITransport 接口和平台工厂,扩展就顺畅很多。

一个比较实用的工厂写法:

public static class TransportFactory { public static ITransport Create() { #if UNITY_WEBGL && !UNITY_EDITOR return new WebSocketTransport(); #else // Editor 下默认走 WebSocket,方便与 WebGL 行为对齐 // 也可以根据配置切换到 TcpTransport 做延迟对比测试 return GameConfig.Instance.UseWebSocket ? new WebSocketTransport() : new TcpTransport(); #endif } }

这个工厂的好处是,业务层只依赖 ITransport 接口,不关心当前跑在什么平台上。项目后续如果新增微信小游戏平台、抖音小游戏平台,只要再写一个适配对应底层的 Transport 实现就行,业务代码完全不用动。

这里讲一个真实的取舍:到底全平台都统一用 WebSocket,还是原生平台继续用 TCP?我的经验是,除非你的项目是对延迟极敏感的帧同步射击游戏,否则全平台统一 WebSocket 是性价比最高的选择。多平台共用一套协议,服务端只需要维护一个 WS 入口,客户端代码也简单很多。只有当你明确测出 WebSocket 的协议开销和表现不能满足需求时,再去为原生平台单独优化 TCP/UDP 通道。

5.2 数据帧协议怎么设计

为了让同一条逻辑消息在不同传输层上都能稳定识别,建议业务层统一设计一套帧协议。我常用的是“消息 ID + 负载长度 + 负载数据”的格式:

  • 前 4 字节:消息 ID,用于路由到不同处理函数
  • 第 5 到 8 字节:负载字节长度
  • 第 9 字节起:负载内容(可以是 JSON 字符串或二进制序列化数据)

示例代码如下:

public static byte[] WrapPayload(int messageId, byte[] payload) { byte[] frame = new byte[8 + payload.Length]; // 使用小端序写入消息 ID frame[0] = (byte)(messageId & 0xff); frame[1] = (byte)((messageId >> 8) & 0xff); frame[2] = (byte)((messageId >> 16) & 0xff); frame[3] = (byte)((messageId >> 24) & 0xff); frame[4] = (byte)(payload.Length & 0xff); frame[5] = (byte)((payload.Length >> 8) & 0xff); frame[6] = (byte)((payload.Length >> 16) & 0xff); frame[7] = (byte)((payload.Length >> 24) & 0xff); Array.Copy(payload, 0, frame, 8, payload.Length); return frame; }

接收端则要先积累满 8 字节头,解析出消息 ID 和长度,再结合当前累计缓冲去截取一条完整业务消息。WebSocket 的二进制消息天然有消息边界,但长度头仍然保留,主要是为了将来切到 TCP 时不用再改协议层,也可以防止接收

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 17:46:05

基于SpringBoot+Vue的博客创作中心实战:草稿到发布的状态管理

很多人做博客系统&#xff0c;容易把重心全放在“文章列表页”和“详情页”的展示效果上&#xff0c;等做到“创作中心”时反而会犹豫&#xff1a;这不就是一个富文本编辑器加一个保存按钮吗&#xff1f;但实际上&#xff0c;创作中心才是一个博客系统里用户停留时间最长、状态…

作者头像 李华
网站建设 2026/9/5 17:45:48

Unity热更新实践:基于HybridCLR的C#热更接入全流程解析

做Unity这么多年&#xff0c;几乎每年都会遇到一次“要不要上热更新”的争论。尤其是线上Bug修复要等包体审核、版本覆盖周期长、玩家一听说又要重新下载几百兆安装包就骂娘的时候&#xff0c;热更新几乎是绕不开的刚需。这两年C#项目的热更方案里&#xff0c;HybridCLR属于热度…

作者头像 李华
网站建设 2026/9/5 17:38:17

纯前端Canvas打字游戏开发实战:从零到上线的完整工程指南

1. 项目概述&#xff1a;从零开始做一款网页游戏1.1 核心需求解析先说结论&#xff1a;我给自己定了一个目标——不用任何游戏引擎&#xff0c;不写一行后端代码&#xff0c;用纯前端技术在两周内做出一款能上线、能让别人打开浏览器就能玩的网页游戏。最后我做出来的是一款打字…

作者头像 李华
网站建设 2026/9/5 17:37:50

从零开发HTML5打砖块游戏:独立开发者的完整实践

1. 一个念头怎么变成一份可执行的需求文档先说一个很多新人容易忽略的事实&#xff1a;做游戏最难的不是写代码&#xff0c;而是把脑子里那个模糊的“好玩”变成一个具体到能动手的东西。我当时的念头特别简单——想做一个不用下载、打开浏览器就能玩的小游戏&#xff0c;能自己…

作者头像 李华
网站建设 2026/9/5 17:23:56

AI游戏开发核心指南:NVIDIA ACE与引擎技术演进全解析

去年年底我帮一个朋友看他做的独立demo&#xff0c;他花了大半年时间搭了一个开放世界的底子&#xff0c;地图、战斗、任务系统都像模像样。我问他NPC做得怎么样了&#xff0c;他苦笑着说了句让我印象特别深的话&#xff1a;“我能让一千个NPC活在地图上&#xff0c;但没法让一…

作者头像 李华