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的資料傳輸。 基本的結構圖如下:
如下說明
- 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的埠傳送。