Socket.IO for Unity 简要介绍和简单应用

在项目中使用到了Socket.IO for unity这个Asset Store上免费的库,这里将简要的介绍一下它的结构,已经使用中的注意事项。

目录结构

Socket.IO for Unity 简要介绍和简单应用_第1张图片
上面为包的目录结构,简单的介绍一下具体的内容:

  • 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的数据传输。 基本的结构图如下:
    Socket.IO for Unity 简要介绍和简单应用_第2张图片

如下说明

  1. SocketIOComponent至少使用了两个独立的线程用于WebSocket数据以及控制的传输,至于为什么使用PingThread (Ping和Pong),这个是WebSocket协议控制的需求,详情请https://tools.ietf.org/html/rfc6455#section-5.5
  2. Connect函数用于连接WebScoket 的服务器, Emit函数用于发送数据到服务器,On函数用于注册回掉函数用于处理来自服务器的报文。
  3. Unity3d的Update线程用于服务器发送而来的报文发送,包含ACK报文(客户端传送给服务器的确认回掉)和Event报文(服务器传送给客户端的报文),对于ACK报文,通常是客户端需要同步的获取服务器的消息的时候使用,在该情况下容易产生死锁问题。 解决的办法是新增加一个单独的线程来处理ACK报文和Event消息。(后面会详细介绍如何实现)
  4. 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函数)。但是其无法让我们从服务器同步的获取消息。 比如说,我们需要从服务器同步的获取数据,当前的实现是无法做到的。修改方法如下:

  1. 客户端
    将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;
    }

  2. 服务器端
    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的端口传送。

你可能感兴趣的:(Unity3d)