通常的Socket都是通過多線程的方式來實現的,多線程需要確保線程安全,而且代碼量也會相對多一些,由於之前已經實現了Unity的協程功能,現在就可以通過協程來實現單線程的Socket了。
首先,封裝一下C#的Socket。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using FirBase;
namespace NxNetwork
{
public class NxSocket
{
private Socket _socket;
public NxSocket()
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public bool Connect(string ip, int port)
{
return Connect(new IPEndPoint(IPAddress.Parse(ip), port));
}
public bool Connect(IPEndPoint ipPoint)
{
bool connected = false;
try
{
FirLog.v(this, "連接服務器:" + ipPoint.Address.ToString());
_socket.NoDelay = true;
_socket.Connect(ipPoint);
_socket.Blocking = false;
connected = true;
}
catch(Exception ex)
{
FirLog.v(this, "連接出現異常:" + ex.Message);
}
return connected;
}
public bool Disconnect()
{
bool success = false;
try
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
success = true;
}
catch (Exception ex)
{
FirLog.v(this, "斷開連接出現異常:" + ex.Message);
}
return success;
}
public bool Send(byte[] buf, int lenght)
{
bool success = false;
try
{
_socket.Send(buf, lenght, SocketFlags.None);
success = true;
}
catch(SocketException ex)
{
FirLog.v(this, "發送出現異常:" + ex.Message);
}
return success;
}
public int Receive(byte[] buf, int offset, int size)
{
return _socket.Receive(buf, offset, size, SocketFlags.None);
}
public int Receive(ref byte[] buf)
{
int lenght = 0;
try
{
lenght = _socket.Receive(buf);
}
catch(Exception ex)
{
FirLog.v(this, "接受出現異常:" + ex.Message);
}
return lenght;
}
public int Available
{
get
{
try
{
if (_socket != null)
{
lock (_socket)
{
return _socket.Available;
}
}
}
catch(Exception ex)
{
FirLog.v(this, "Available出現異常:" + ex.Message);
}
return 0;
}
}
public bool Connected
{
get
{
if(_socket != null && _socket.Connected)
{
return true;
}
return false;
}
}
public void Dispose()
{
if(_socket != null)
{
lock(_socket)
{
if (_socket.Connected)
{
_socket.Shutdown(SocketShutdown.Both);
}
_socket.Close();
_socket = null;
}
}
FirLog.v(this, "析構Socket");
}
}
}
這只是包裝了一些Socket的接口,可以看到,我們包裝的這個Socket是通過NoBlock的方式來接受數據的,所以接收數據的方法Receive不會阻塞線程,這就給我們的協程提供了可能。
下面是通過協程實現的NetworkManager
using FirBase;
using NxNetwork.MSG;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NxNetwork
{
public class NetworkNoThreadManager : FirSingleton<NetworkNoThreadManager>
{
private NxSocket _socket = new NxSocket();
private byte[] _recvData = new byte[GlobalVar.BUFFER_LENGTH]; //消息接受緩存
private byte[] _recvDataTmp = new byte[GlobalVar.BUFFER_LENGTH];
private UInt32 _recvOffset = 0;
Dispatcher _commDispatcher = new Dispatcher();
public NetworkNoThreadManager()
{
}
public void RegisterMsg<T>(CallBack<T> handle)
{
_commDispatcher.Reg(handle);
}
public void Init()
{
FirTask.Instance.AddTask(Recv());
}
public void ConnectServer(string ip, int port)
{
_socket.Connect(ip, port);
}
public void SendMsg<T>(T data)
{
var bData = SerializationHelper.Serialization(data);
ProtoMsg msg = new ProtoMsg(bData.Length, DateTime.Now.ToBinary(), data.GetType().Name, bData);
var msgData = msg.ToBinary();
SendMsg(msgData);
}
public void SendMsg(byte[] data)
{
_socket.Send(data, data.Length);
if (_socket == null || !_socket.Connected)
{
//TODO 重連
}
}
private IEnumerator Recv()
{
while (true)
{
if (_socket != null && _socket.Connected)
{
if (_socket.Available > 0)
{
int receiveLength = _socket.Receive(ref _recvData);
if (receiveLength != 0)
{
Array.Copy(_recvData, 0, _recvDataTmp, _recvOffset, receiveLength);
_recvOffset += (UInt32)receiveLength;
if (_recvOffset > GlobalVar.HEAD_SIZE)
{
byte[] receiveData = new byte[_recvOffset];
Array.Copy(_recvDataTmp, receiveData, _recvOffset);
Array.Clear(_recvDataTmp, 0, _recvDataTmp.Length);
_recvOffset = 0;
RecvDataHandle(receiveData);
}
}
}
}
yield return new WaitForEndOfFrame();
}
}
private void RecvDataHandle(byte[] receiveData)
{
ProtoMsg msg = SerializationHelper.BinaryDeSerialization<ProtoMsg>(receiveData);
byte[] data = new byte[msg.Lenght];
Array.Copy(msg.MsgBuffer, data, msg.Lenght);
_commDispatcher.MsgDispatcher(msg.MsgName, data);
}
}
}
在main函數需要這樣啓動
NetworkNoThreadManager.Instance.Init();
NetworkNoThreadManager.Instance.ConnectServer("127.0.0.1", 8885);
之後我們可以像上一篇博客那樣註冊消息,首先建立一個消息處理類:
class LoginHandle
{
public LoginHandle()
{
NetworkNoThreadManager.Instance.RegisterMsg(new CallBack<UserInfo>(onUserInfo));
}
public void onUserInfo(UserInfo info)
{
FirLog.v(this, "接受到用戶信息:" + info.accid + "密碼:" + info.pwd);
}
}
然後同樣在main函數中創建這個類。
LoginHandle handle = new LoginHandle();
然後就可以接受消息了
源碼以及測試工程
http://download.csdn.net/detail/baijiajie2012/9481989