TCP、UDP 在 Windows、Android、iOS 上都很好理解,直接创建 Socket 就行。
但到了 WebGL,情况完全变了:代码最终运行在浏览器里,不能继续照搬原生 Socket 模型。
MyFramework 因此把 WebSocket 分成了两条底层实现:
bash
Native平台
NetConnectWebSocket
↓
ClientWebSocket
WebGL
NetConnectWebSocketWebGL
↓
NativeWebSocket
↓
JavaScript WebSocket
但更有意思的是:底层虽然完全不同,上层 NetPacket、序列化、心跳、状态机和业务执行却仍然可以保持一致。
项目地址:
一、非 WebGL:直接使用 ClientWebSocket
普通平台的核心对象就是:
bash
protected ClientWebSocket mWebSocket;
连接时:
bash
mWebSocket = new();
foreach (var item in mHeader)
{
mWebSocket.Options.SetRequestHeader(
item.Key,
item.Value);
}
await mWebSocket.ConnectAsync(
new Uri(mURL),
default);
所以:
bash
Windows
Android
iOS
↓
.NET ClientWebSocket
↓
WebSocket服务器
连接完成以后统一进入:
bash
CONNECTING
↓
CONNECTED
框架自己维护 NET_STATE,业务层不需要直接判断 WebSocketState。
二、WebGL 为什么必须单独实现?
WebGL 版本不再使用:
bash
System.Net.WebSockets.ClientWebSocket
而是:
bash
protected WebSocket mWebSocket;
这个 WebSocket 来自框架中的 NativeWebSocket。
在 WebGL 真机构建下,最终会调用:
bash
[DllImport("__Internal")]
public static extern int WebSocketConnect(int instanceId);
[DllImport("__Internal")]
public static extern int WebSocketSend(
int instanceId,
byte[] dataPtr,
int dataLength);
也就是:
bash
C#
↓
WebAssembly
↓
JSLIB
↓
Browser WebSocket API
浏览器负责真正的网络连接。
这就是 WebGL 网络和原生平台最大的区别。
三、WebGL 接收数据靠事件,而不是 ReceiveAsync 循环
Native 版本使用:
bash
await mWebSocket.ReceiveAsync(...);
持续等待服务器数据。
WebGL 版本则注册:
bash
mWebSocket.OnOpen += ...
mWebSocket.OnError += ...
mWebSocket.OnClose += ...
mWebSocket.OnMessage += ...
真正收到数据时:
bash
mWebSocket.OnMessage += (byte[] data) =>
{
mInputBuffer.addData(
data,
data.Length);
...
};
因此两边的底层模型是完全不同的:
bash
Native
await ReceiveAsync()
↓
收到数据
WebGL
Browser收到数据
↓
JavaScript回调
↓
OnMessage
但下一层马上又被统一了。
四、不管数据从哪里来,都先进入 StreamBuffer
WebGL 的 OnMessage 收到数据以后:
bash
mInputBuffer.addData(
data,
data.Length);
随后继续:
bash
while (true)
{
PARSE_RESULT result =
preParsePacket(...);
if (result != PARSE_RESULT.SUCCESS)
{
break;
}
mReceiveBuffer.Enqueue(...);
mInputBuffer.removeData(
0,
index);
}
这和前面 TCP 的思路非常接近:
bash
底层收到字节
↓
StreamBuffer
↓
preParsePacket
↓
拆出完整NetPacket
↓
ReceiveBuffer
所以 Native 和 WebGL 真正不同的只是:
数据怎么进入框架。
进入框架以后,处理链又重新统一了。
五、业务逻辑仍然只在 update() 中执行
WebGL 回调收到消息以后并不会直接:
bash
packet.execute();
而是先:
bash
mReceiveBuffer.Enqueue(...);
然后在主线程 update():
bash
while (mReceiveBuffer.Count > 0)
{
PacketReceiveInfo info =
mReceiveBuffer.Dequeue();
NetPacket packet =
parsePacket(...);
packet.execute();
mNetPacketFactory.destroyPacket(packet);
}
最终仍然是:
bash
浏览器网络事件
↓
ReceiveBuffer
================
Unity主线程
↓
反序列化
↓
NetPacket.execute()
这样 WebGL 和 Native 平台的业务层完全不需要写两套逻辑。
六、发送也统一成"先排队,再真正发送"
业务层调用:
bash
sendNetPacket(packet);
协议先完成序列化,然后进入:
bash
mOutputBuffer.Enqueue(...);
Native 版本:
bash
await mWebSocket.SendAsync(
new ArraySegment<byte>(...),
mMessageType,
true,
CancellationToken.None);
WebGL 版本:
bash
await mWebSocket.Send(
info.mData,
info.mDataSize);
所以又变成:
bash
业务层
↓
NetPacket
↓
序列化
↓
OutputBuffer
↓
平台相关Send
差异被封锁在最底层。
七、WebSocket 不只有 JSON
很多人一提 WebSocket 就默认:
bash
JSON字符串
但 MyFramework 实际提供了不同实现。
例如:
bash
NetConnectWebSocketByte
NetConnectWebSocketJson
WebGL:
NetConnectWebSocketWebGLByte
NetConnectWebSocketWebGLBit
也就是说 WebSocket 只是:
传输通道。
上层到底传:
bash
JSON
普通Byte序列化
Bit压缩二进制
是另外一件事。
例如 NetConnectWebSocketByte 仍然会写入:
bash
PacketSize
PacketType
Sequence
HasSign
FieldFlag
PacketBody
然后继续使用:
bash
SerializerWrite
SerializerRead
所以前面讲过的网络协议体系,并不会因为换成 WebSocket 就全部推倒重来。
八、JSON WebSocket 则走另一条路线
NetConnectWebSocketJson 会把消息包装成:
bash
WebSocketPacketBodyJson body = new(
msgType,
netPacket.writeContent(),
timeStamp);
最后:
bash
JsonConvert.SerializeObject(body)
消息结构更接近:
bash
message_type
data
time
接收后再通过:
bash
mPacketTypeStringList
找到对应消息类。
这类方案更适合:
bash
调试方便
协议可读
对接第三方服务器
不极端追求带宽
而前面的 Bit / Byte 版本则更偏游戏内部协议。
九、心跳和连接状态也没有分裂
两套 WebSocket 都维护:
bash
MyTimer mPingTimer;
Action mPingCallback;
NET_STATE mNetState;
连接成功:
bash
CONNECTED
↓
启动PingTimer
定时:
bash
if (mPingTimer.tickTimer(elapsedTime))
{
mPingCallback.Invoke();
}
断线则统一转换成:
bash
SERVER_CLOSE
NET_CLOSE
NONE
再通过网络状态命令通知上层。
因此业务系统面对的是:
bash
NET_STATE
而不是:
bash
这是ClientWebSocket错误?
还是Browser WebSocket错误?
十、真正的全平台网络抽象是什么
最终结构可以压缩成:
bash
NetPacket
↓
Serializer / JSON
↓
WebSocket层
↙ ↘
Native WebGL
↓ ↓
ClientWebSocket NativeWebSocket
↓ ↓
OS Socket Browser JS API
↘ ↙
Server
这套设计真正有价值的地方,不是"支持了 WebSocket"。
而是:
把平台差异压到了网络最底层。
上面的:
bash
NetPacket
PacketType
序列化
心跳
连接状态
消息队列
主线程execute
仍然保持同一套模型。
所以当一个 Unity 项目需要同时跑:
bash
Windows
Android
iOS
WebGL
时,业务代码不需要因为浏览器不能直接使用原生 Socket,就重新写一套网络系统。
底层可以完全不同,但上层绝不能跟着一起分裂。