1. 程式人生 > >Socket.IO for Unity 簡要介紹和簡單應用

Socket.IO for Unity 簡要介紹和簡單應用

在專案中使用到了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的資料傳輸。 基本的結構圖如下:
    這裡寫圖片描述

如下說明

  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的埠傳送。