Unity3d-c# Socket非同步通訊與Unity元件資料更新的處理
阿新 • • 發佈:2019-02-01
首先基於C#的Socket的BeginReceive非同步接收和BeginSend非同步傳送資料的底層的實現也是多執行緒處理,當然也可以自己用執行緒來實現;C#的非同步Socket簡單的例子和教程網上很多,在此就不再累贅了;結合剛開始說的,C#的非同步Socket實際是多執行緒實現,那麼我們在Unity中使用的時候就會遇到我們不能在C#的Socket的非同步回撥函式中訪問Unity的元件的問題;一開始我試了用事件來處理,就是Socket接收到訊息,觸發一個事件,在事件回撥函式訪問Unity的元件,然而這是不可行,查了一下資料瞭解到事件的回撥是在呼叫執行緒執行的,也就是說兜了一個圈還是在子執行緒執行,還是不能訪問Unity的元件;涉及到多執行緒資料的處理,很多時候都會想到使用生產者-消費者模式
首先訊息簡單定義:
public abstract class Message{ public static T Parse<T>(byte[] data)where T:Message { \\TODO 解析資料,根據自己的資料定義,可以使用工廠模式來產生不同的Message子類 } }
接下來是訊息容器:訊息容器比較簡單就是一個Message佇列
using MessageBuffer = System.Collections.Generic.Queue<Message>;
接下來非同步Socket
public class MessageProducer{ public Socket LocalSocket; public byte[] ReceiveBuffer = new byte[1*1024]; public List<byte> RecieveBytes = new List<byte>(); public MessageBuffer Messages = new MessageBuffer(); //TODO 其他的屬性、Socket的初始化、連線到伺服器,傳送資料等,在此就不寫了</span> public void BeginReceive(){<span style="font-family: Arial, Helvetica, sans-serif;"> </span> <span style="font-family: Arial, Helvetica, sans-serif;">try</span> { socket.BeginReceive(RecieveBuffer,0,1*1024,0 ,new AsyncCallback(OnReceive),this); } catch{ //TODO 接收錯誤處理 } } private void OnReceive(IAsyncResult iar){ MessageProducer producer = iar.AsyncState as MessageProducer; try{int read = producer.LocalSocket.EndReceive(iar); if(read > 0){ byte[] retbytes = new byte[read]; Array.ConstrainedCopy(producer.ReceiveBuffer ,0,retbytes,0,read); <span style="white-space:pre"> </span>producer.RecieveBytes.AddRange(retbytes); <span style="white-space:pre"> </span>if(producer.LocalSocket.Available <= 0){ <span style="white-space:pre"> </span>//TODO 接受完畢,把資料變成Message,正常情況還需要處理粘包,丟包的問題, <span style="white-space:pre"> </span>//這裡假設接收到的都是一個單獨完整的資料</span> <span style="white-space:pre"> </span> Message msg = Message.Parse<ChildOfMessage>(producer.RecieveBytes.ToArray()); <span style="white-space:pre"> </span>RecieveBytes.Clear(); <span style="white-space:pre"> </span>producer.Messages.Enqueue(msg); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>producer.LocalSocketBeginReceive(RecieveBuffer,0,1*1024,0 ,new AsyncCallback(OnReceive),pproducer);//繼續接收資料 <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>else{ <span style="white-space:pre"> </span>//伺服器斷開 <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>catch{ <span style="white-space:pre"> </span>//TODO 接收錯誤處理</span> <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}//到此,生產的行為結束</span> }
接下就是Unity 主執行緒處理訊息的程式碼:
public class MessageConsumer: MonoBehaviour{
private MessageProducer messageproducer = new MessageProducer();</span>
void Start(){
//TODO 生產者的初始化
messageproducer.BeginReceive();//開始接受資料
}
//TODO 這裡簡單的使用Update函式來檢查資料
void Update(){
while(messageproducer.Messages.Count > 0){
Message message = producer.Messages.Dequeue();
//TODO 處理訊息,具體的就根據需求來實現了,在此不再多寫了
}
}
到這裡,主要的程式碼邏輯就算是完成了;當然這還不算完,這裡會有一個問題就是如果在手機上手機進入睡眠狀態、鎖屏、程式切換等會造成Update方法不執行,但是Socket還是會不停的接收資料,時間長了就會積累很多資料,當切換回遊戲是就會需要處理很多訊息,有可能很多重複的資料重新整理處理,會不會造成卡頓等還沒有實際驗證過;
到此為止,此文純屬拋磚引玉,我相信大神們肯定有更好的方法,望賜教!拜謝!