1. 程式人生 > >Unity3d-c# Socket非同步通訊與Unity元件資料更新的處理

Unity3d-c# Socket非同步通訊與Unity元件資料更新的處理

首先基於C#的Socket的BeginReceive非同步接收和BeginSend非同步傳送資料的底層的實現也是多執行緒處理,當然也可以自己用執行緒來實現;C#的非同步Socket簡單的例子和教程網上很多,在此就不再累贅了;結合剛開始說的,C#的非同步Socket實際是多執行緒實現,那麼我們在Unity中使用的時候就會遇到我們不能在C#的Socket的非同步回撥函式中訪問Unity的元件的問題;一開始我試了用事件來處理,就是Socket接收到訊息,觸發一個事件,在事件回撥函式訪問Unity的元件,然而這是不可行,查了一下資料瞭解到事件的回撥是在呼叫執行緒執行的,也就是說兜了一個圈還是在子執行緒執行,還是不能訪問Unity的元件;涉及到多執行緒資料的處理,很多時候都會想到使用生產者-消費者模式

來非同步處理資料,這裡我們就可以把非同步Socket的執行緒當作生產者,Unity主執行緒作為消費者,首先要做的就是建立一個數據容器來存放非同步Socket接收到的資料,然後主執行緒不斷的來取資料,如果有資料,那麼就把資料交付給對應的處理邏輯;我想的一個簡單的實現就是建立一個專門用來派發接收到的資料的元件,也就是一個MonoBehaviour,然後使用每幀執行、固定時間執行函式(update,lateupdate,fixedupdate)或者使用協程來檢測資料容器是否有資料,如果有資料就交付給相應的處理邏輯,想法大概就是這樣,如下是主要邏輯的簡單實現,見笑了!

首先訊息簡單定義:

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還是會不停的接收資料,時間長了就會積累很多資料,當切換回遊戲是就會需要處理很多訊息,有可能很多重複的資料重新整理處理,會不會造成卡頓等還沒有實際驗證過;


到此為止,此文純屬拋磚引玉,我相信大神們肯定有更好的方法,望賜教!拜謝!