在项目中使用到了Socket.IO for unity这个Asset Store上免费的库,这里将简要的介绍一下它的结构,已经使用中的注意事项。
目录结构
上面为包的目录结构,简单的介绍一下具体的内容:
- JSONObject - 打包与解析JSON格式
- Prefabs - 简单的SocketIO客户端的Prefab,实际上其就是一个attach了SocketIOComponent的Unity GameObject
- Scences - Unity3d的测试Scene,用于简单测试
- Scripts - Unity3d的MonoBehavior 脚本和其使用到的类, 其实只有一个SocketIOComponent是脚本,其他都是该脚本使用到的帮助类
- Server - 目录下的存放的是NodeJs服务器端的测试Js脚本,对于于客户端的测试用例,在实际的开发中可以删除。
- WebSocketSharp- 目录下存放的是C#的WebSocket的实现,不依赖于任何Unity3d的代码。
- readme.txt - 简单的帮助文档,说明如何使用该package。
核心类解析
- SocketIOComponent
SocketIOComponent这个脚本是我们使用该Socket.IO for Unity package最重要的一个类,其集成了报文的封装,解析,回掉函数,Ping,Pong控制帧,以及WebSocket的数据传输。 基本的结构图如下:
如下说明
- SocketIOComponent至少使用了两个独立的线程用于WebSocket数据以及控制的传输,至于为什么使用PingThread (Ping和Pong),这个是WebSocket协议控制的需求,详情请https://tools.ietf.org/html/rfc6455#section-5.5
- Connect函数用于连接WebScoket 的服务器, Emit函数用于发送数据到服务器,On函数用于注册回掉函数用于处理来自服务器的报文。
- Unity3d的Update线程用于服务器发送而来的报文发送,包含ACK报文(客户端传送给服务器的确认回掉)和Event报文(服务器传送给客户端的报文),对于ACK报文,通常是客户端需要同步的获取服务器的消息的时候使用,在该情况下容易产生死锁问题。 解决的办法是新增加一个单独的线程来处理ACK报文和Event消息。(后面会详细介绍如何实现)
- SocketIOEvent和JSONObject用于SocketIOComponent的用户的报文的发送与接收。SocketIOEvent如下
// 事件的名称
public string name { get; set; }
// 具体的数据内容
public JSONObject data { get; set; }
public SocketIOEvent(string name) : this(name, null) { }
public SocketIOEvent(string name, JSONObject data)
{
this.name = name;
this.data = data;
}
public override string ToString()
{
return string.Format("[SocketIOEvent: name={0}, data= {1}]", name, data);
}
}
}
如何封装同步函数
SocketIOComponent提供了很简介的方法给我们向服务器发送消息(SocketIOComponent::Emit函数)和服务器接收消息(SocketIOComponent::On函数)。但是其无法让我们从服务器同步的获取消息。 比如说,我们需要从服务器同步的获取数据,当前的实现是无法做到的。修改方法如下:
客户端
将SocketIOComponent的Update函数的处理移动到一个新增加的线程中,不依赖于Unity3d的线程。如下:
public void Connect()
{
connected = true;socketThread = new Thread(RunSocketThread); socketThread.Start(ws); pingThread = new Thread(RunPingThread); pingThread.Start(ws); *// 新增加callback线程 callbackThread = new Thread(RunCallbackThread); callbackThread.Start(ws);* }
private void RunCallbackThread(object obj)
{
while(connected)
{
// check the msg queue count with the time.
msgSemaphore.WaitOne(600);
if (eventQueue.Count > 0 || ackQueue.Count > 0 || wsConnected != ws.IsConnected)
{
lock (eventQueueLock)
{
while (eventQueue.Count > 0)
{
EmitEvent(eventQueue.Dequeue());
}
}lock (ackQueueLock) { while (ackQueue.Count > 0) { InvokeAck(ackQueue.Dequeue()); } } if (wsConnected != ws.IsConnected) { wsConnected = ws.IsConnected; if (wsConnected) { EmitEvent("connect"); } else { EmitEvent("disconnect"); } } } // GC expired acks if(ackList.Count == 0) { continue; } if(DateTime.Now.Subtract(ackList[0].time).TotalSeconds < ackExpirationTime) { continue; } ackList.RemoveAt(0); }
// 同步的从服务器端获取数据
private void GetDataFromServerBySync()
{
webSocketWaitEvent.Reset();
SocketIoComponent.Emit(‘testSync’, (JSONObject jsonData) => {
// The lib always use the to wrapper the json data.
hallManagerData = HallJson.ToHallManagerData(jsonData [0]);
webSocketWaitEvent.Set();
});
webSocketWaitEvent.WaitOne(500);
return;
}服务器端
io.sockets.on(‘connection’, function(socket){
console.log(‘connection is called!’);
console.log(socket.id)socket.on( ‘testSync’, function(callback){
// 回调函数
callback(testJson);
});
});
3 做此修改后请注意,回调函数将不在Unity3d的主线程中处理,所以还需要做一点的封装,将最后的事件放入Update线程中处理。 如果没有同步从Server端获取数据的需求,就不要做上述的修改了。
经过初步测试,从服务器端获取超过10k的数据,大约需要4ms左右。服务器为NodeJs,在本地通过127.0.0.1的端口传送。