通常的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