【Unity&C#】運行在主線程的Socket

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章