在網絡游戲開發中,一些游戲需要使用長連接的方式進行網絡通信,即使用Socket建立長連接。那么在Unity3d中,如何使用C#與服務端建立長連接呢?為什么 要說使用異步呢?我們知道,在Unity3d中,每個游戲畫面的播放都是以帖的概念循環播放的。而且只能在UI線程中播放,在其它線程不可以操作UI有關的東西,這都是網絡通信需要解決的問題。
使用Socket創建連接
眾所周知,在游戲客戶端啟動之后,一定有一個時機是創建網絡連接的,比如一般是選游戲大區這后,或用戶點擊進入游戲時,這都是由UI層觸發點擊和創建網絡連接的。但是網絡連接是一個IO阻塞操作,如果直接使用Socket的同步方法,會卡住當前UI線程,導致游戲畫面出現卡頓現象。所以要么使用協程,要么便需要使用異步創建連接。為了方便創建socket連接,這里使用異步連接方式。如下面代碼所示:
public void ConnectServer()
{
this.Close();
try
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();//創建連接參數對象
this.endPoint = new IPEndPoint(IPAddress.Parse(host), port);
args.RemoteEndPoint = this.endPoint;
args.Completed += OnConnectedCompleted;//添加連接創建成功監聽
_socket.ConnectAsync(args); //異步創建連接
}
catch(Exception e)
{
Debug.Log("服務器連接異常:" + e);
}
}
private void OnConnectedCompleted(object sender,SocketAsyncEventArgs args)
{
try
{ ///連接創建成功監聽處理
if (args.SocketError != SocketError.Success)
{
//通知上層連接失敗
CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectFailed);
}
else
{
Debug.Log("網絡連接成功線程:" + Thread.CurrentThread.ManagedThreadId.ToString());
//通知上層連接創建成功
CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectedSucess);
StartReceiveMessage(); //啟動接收消息
}
}
catch(Exception e)
{
Debug.Log("開啟接收數據異常" + e);
}
}
發送消息
一般來說,消息的發送都是UI層觸發的,比如點擊某個按鈕,執行了某個操作,就會向服務器獲取或保存數據。所以也需要使用協程或異步的方式發送消息,這里使用異步的方式。如下面代碼所示:
public void SendToServer(byte[] data)
{
try
{
if (_socket == null || !_socket.Connected)
{
Debug.Log("socket未連接,發送消息失敗");
return;
}
//創建發送參數
SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs();
sendEventArgs.RemoteEndPoint = endPoint;
//設置要發送的數據
sendEventArgs.SetBuffer(data, 0, data.Length);
//異步發送數據
_socket.SendAsync(sendEventArgs);
}
catch(Exception e)
{
Debug.Log("發送數據異常:" + e);
}
}
由於Socket制作是一種非常底層的操作,所以這里直接發送byte[],具體發送什么樣的數據,由應用層決定。
接收數據
由於我們使用的是Socket自帶的異步創建連接,發送數據,所以接收數據也是在socket的異步線程中。如下面代碼所示:
private void StartReceiveMessage()
{
//啟動接收消息
SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
//設置接收消息的緩存大小,正式項目中可以放在配置 文件中
byte[] buffer = new byte[1024 * 512];
//設置接收緩存
receiveArgs.SetBuffer(buffer, 0, buffer.Length);
receiveArgs.RemoteEndPoint = this.endPoint;
receiveArgs.Completed += OnReceiveCompleted; //接收成功
_socket.ReceiveAsync(receiveArgs);//開始異步接收監聽
}
public void OnReceiveCompleted(object sender,SocketAsyncEventArgs args)
{
try
{
Debug.Log("網絡接收成功線程:" + Thread.CurrentThread.ManagedThreadId.ToString());
if (args.SocketError == SocketError.Success && args.BytesTransferred > 0)
{
//創建讀取數據的緩存
byte[] bytes = new byte[args.BytesTransferred];
//將數據復制到緩存中
Buffer.BlockCopy(args.Buffer, 0, bytes, 0, bytes.Length);
if(ReceiveSocketDataHandler == null)
{
Debug.Log("沒有處理接收消息的事件 ");
}
//接收數據成功,調上層處理接收數據的事件
ReceiveSocketDataHandler?.Invoke(bytes);
//再次啟動接收數據監聽,接收下次的數據。
StartReceiveMessage();
}
else
{
CompleteConnectServerHandler(ConnectedStatus.Disconnect);
}
}
catch(Exception e)
{
Debug.Log("接收數據異常:" + e);
}
}
接收數據的方法會在連接創建成功時調一次,它一直監聽服務器的數據到來。當接收到服務器的數據時,我們會調用上層處理數據的事件 ,即ReceiveSocketDataHandler?.Invoke(bytes);
,首先要做的就是對接收的數據進行斷包和粘包處理。這個在下篇文章中再說。
詳細的代碼,可以參考項目源碼:http://www.xinyues.com/h-nd-195.html#_np=2_627
可以關注微信公眾號,及時接收文章推送