Unity处理Socket粘包拆包

现在游戏协议的数据格式基本上都是用protobuf协议格式,而protobuf最后会转换为二进制,所以这个例子实现的逻辑的也是二进制的处理。

处理粘包拆包的逻辑主要是在DecodePackage方法中。

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Msg;
using UnityEngine;
using UnityEngine.Events;

namespace Billiard.Server
{
    public class SocketMgr : SingleMgr<SocketMgr>
    {
        private bool m_nYesInit = false;
        private Queue<byte[]> m_SendQueue = new Queue<byte[]>();
        private Queue<byte[]> m_ReceiveQueue = new Queue<byte[]>();
        private MemoryStream m_ReceiveStream = null;
        private bool m_IsConnected = false;
        private string m_IP = string.Empty;
        private int m_Port = int.MaxValue;
        private Socket m_Socket = null;
        private IPEndPoint endPoint;
        private bool sendCompletedAsync = true;
        private bool receiveCompletedAsync = true;
        private readonly object IncomingMessageLock = new object();
        private readonly object OutcomingMessageLock = new object();
        private byte[] tempBufferArr = new byte[0];
        private Thread sendThread;
        private Thread receiveThread;
        private SocketAsyncEventArgsPool m_readWritePool;

        public void Init()
        {
            if (!m_nYesInit)
            {
                OnMsg<object>((int) ENetworkMessage.Net_SyncEntityID, OnSyncEntityID);
                OnMsg<object>((int) ENetworkMessage.Net_HeartBeat, OnHeartBeat);
                OnMsg<object>((int) ENetworkMessage.Net_HadConnect, OnNetHadConnect);
            }

            m_nYesInit = true;
        }

        public void Connect(string ip, int port)
        {
            m_IP = ip;
            m_Port = port;
            m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            endPoint = new IPEndPoint(IPAddress.Parse(m_IP), m_Port);

            try
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs(); //创建连接参数对象
                args.RemoteEndPoint = endPoint;
                args.Completed += OnConnectedCompleted; //添加连接创建成功监听
                m_Socket.ConnectAsync(args);

                m_ReceiveStream = new MemoryStream();
            }
            catch (Exception e)
            {
                Debug.LogWarning("连接错误! " + e.Message);
                CloseSocket();
            }
        }

        private void OnConnectedCompleted(object sender, SocketAsyncEventArgs args)
        {
            try
            {
                if (args.SocketError != SocketError.Success)
                {
                    CloseSocket();
                }
                else
                {
                    Debug.Log(
                        $"连接服务器成功! ip:{args.ConnectSocket.LocalEndPoint} ThreadId:{Thread.CurrentThread.ManagedThreadId.ToString()}");
                    m_IsConnected = true;
                    m_readWritePool = new SocketAsyncEventArgsPool(10, IoCompleted);

                    sendThread = new Thread(SendMsgThread);
                    sendThread.IsBackground = true;
                    sendThread.Start();
                    receiveThread = new Thread(ReceiveMsgThread);
                    receiveThread.IsBackground = true;
                    receiveThread.Start();

                    doSyncEntityID();
                }
            }
            catch (Exception e)
            {
                Debug.Log("开启接收数据异常" + e.Message);
                CloseSocket();
            }
        }

        private void ReceiveMsgThread(object obj)
        {
            while (m_IsConnected)
            {
                if (m_Socket.Available > 0)
                {
                    if (!receiveCompletedAsync )
                    {
                        Debug.LogWarning("---<<< 异步任务还未处理完成!");
                        return;
                    }

                    try
                    {
                        SocketAsyncEventArgs e = m_readWritePool.Pop();
                        byte[] buffer = new byte[8192];
                        e.SetBuffer(buffer, 0, buffer.Length);
                        receiveCompletedAsync = m_Socket.ReceiveAsync(e);
                    }
                    catch (Exception e)
                    {
                        Debug.LogWarning($"<<< error! {e} | 当前容量:{m_readWritePool.Count}");
                    }
                }
            }
        }

        public void ReceiveCallback(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                if (e.BytesTransferred <= 0)
                {
                    Debug.Log("---<<< 空数据不处理!");
                    return;
                }

                byte[] bytes = new byte[e.BytesTransferred];
                Buffer.BlockCopy(e.Buffer, 0, bytes, 0, bytes.Length);
                lock (IncomingMessageLock)
                {
                    m_ReceiveQueue.Enqueue(bytes);
                }
            }
            else
            {
                //TODO 
            }
        }

        private void SendMsgThread(object obj)
        {
            while (m_IsConnected)
            {
                //如果消息队列中有消息,则发送消息
                if (m_SendQueue.Count > 0)
                {
                    if (!sendCompletedAsync)
                    {
                        Debug.LogWarning("--->>> 异步发送io还未处理完成!");
                        return;
                    }

                    lock (OutcomingMessageLock)
                    {
                        try
                        {
                            byte[] sendBuffer = m_SendQueue.Dequeue();
                            SocketAsyncEventArgs e = m_readWritePool.Pop();
                            e.SetBuffer(sendBuffer, 0, sendBuffer.Length);
                            sendCompletedAsync = m_Socket.SendAsync(e);
                        }
                        catch (Exception e)
                        {
                            Debug.LogWarning($">>> error! {e.Message}");
                        }
                    }
                }
            }
        }

        private void SendCallback(SocketAsyncEventArgs e)
        {
            if (!m_Socket.Connected)
            {
                OnClose();
                return;
            }

            if (e.SocketError == SocketError.Success)
            {
                sendCompletedAsync = true;
            }
            else
            {
                Debug.LogWarning($"--->>> 发送数据失败!");
            }
        }

        void IoCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ReceiveCallback(e);
                    m_readWritePool.Push(e);
                    break;
                case SocketAsyncOperation.Send:
                    SendCallback(e);
                    m_readWritePool.Push(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }
        }

        public void MsgUpdate()
        {
            if (m_ReceiveQueue.Count > 0)
            {
                lock (IncomingMessageLock)
                {
                    DecodePackage(m_ReceiveQueue.Dequeue());
                }
            }
        }

        private void DecodePackage(byte[] buffer)
        {
            int length = buffer.Length;

            //处理拆包、粘包
            if (length >= OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE)
            {
                if (tempBufferArr.Length > 0)
                {
                    buffer = tempBufferArr.Concat(buffer).ToArray();
                    length = buffer.Length;

                    //Array.Clear(temgBufferArr,0,temgBufferArr.Length);
                    tempBufferArr = new byte[0];
                }

                m_ReceiveStream.Write(buffer, 0, length);
                m_ReceiveStream.Position = 0;

                while (m_ReceiveStream.Length != 0)
                {
                    int oneMsgLength = (int) BitConverter.ToUInt32(buffer,
                        (int) m_ReceiveStream.Position + OFFSET_SIZE + BYTE_SIZE);
                    if (length >= OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE + oneMsgLength)
                    {
                        int oneBufferLength = OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE + oneMsgLength;
                        byte[] oneMsgBuffer = new byte[oneBufferLength];
                        m_ReceiveStream.Read(oneMsgBuffer, 0, oneBufferLength);
                        try
                        {
                            ReceivePacket(oneMsgBuffer);
                        }
                        catch (Exception e)
                        {
                            //do nothing 不处理业务层异常逻辑
                        }
                    }
                    else
                    {
                        //Debug.LogWarning($"---<<< 本次消息包长度不够,暂不处理!");
                        int leftLength = length - (int) m_ReceiveStream.Position;
                        tempBufferArr = new byte[leftLength];
                        Buffer.BlockCopy(buffer, (int) m_ReceiveStream.Position, tempBufferArr, 0, leftLength);
                        break;
                    }

                    if (m_ReceiveStream.Position == m_ReceiveStream.Length)
                    {
                        break;
                    }
                }

            }
            else
            {
                Debug.LogWarning($" 包大小 : {length} 消息号 : {BitConverter.ToUInt32(buffer, OFFSET_SIZE)}");
                //TODO 
            }

            m_ReceiveStream.SetLength(0);
            m_ReceiveStream.Seek(0, SeekOrigin.Begin);
            receiveCompletedAsync = true;
        }

        private const int OFFSET_SIZE = 8;
        private const int BYTE_SIZE = 4;
        private byte[] msgIDBytes = new byte[BYTE_SIZE];
        private byte[] msgLenBytes = new byte[BYTE_SIZE];

        public void ReceivePacket(byte[] readBuff)
        {
            for (int i = 0; i < BYTE_SIZE; i++)
            {
                msgIDBytes[i] = readBuff[OFFSET_SIZE + i];
            }

            uint msgID = BitConverter.ToUInt32(msgIDBytes, 0);

            for (int i = 0; i < BYTE_SIZE; i++)
            {
                msgLenBytes[i] = readBuff[OFFSET_SIZE + BYTE_SIZE + i];
            }

            uint msgLen = BitConverter.ToUInt32(msgLenBytes, 0);
            if (msgID != 100006)
                Debug.Log("接收消息 :" + msgID + " 消息 :" + msgLen);

            IMessage rspPacket = null;
            if (msgID == (uint) ENetworkMessage.Net_SyncEntityID
                || msgID == (uint) ENetworkMessage.Net_HeartBeat
                || msgID == (uint) ENetworkMessage.Net_HadConnect)
            {
                //特殊处理,不处理回包仅转发
            }
            else
            {
                int StartIdx = OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE;
                int PacketSize = readBuff.Length - StartIdx;

                // 长度校验
                if (StartIdx + msgLen != readBuff.Length)
                {
                    Debug.LogError(
                        $"pack size error! msgID : {msgID} StartIdx : {StartIdx} msgLen : {msgLen} BuffLegnth : {readBuff.Length}");
                    return;
                }

                rspPacket = GameServerMgr.DecodeMsg((MsgTile) msgID, StartIdx, PacketSize, readBuff);
                if (rspPacket == null)
                {
                    Debug.LogWarning(" Unpack Error! MsgID :" + msgID);
                    return;
                }
            }

            DispatchMsgData<object>((int) msgID, rspPacket);
        }

        public void SendMsg(MsgTile networkMessage, byte[] packet)
        {
            if (m_Socket == null)
            {
                return;
            }

            //判断Websocket状态
            if (!m_Socket.Connected)
            {
                return;
            }

            byte[] offset1 = MiniConverter.IntToBytes(2 + 2 + 4 + 4 + packet.Length);
            byte[] offset2 = MiniConverter.IntToBytes(1);
            byte[] offset3 = MiniConverter.IntToBytes(2);
            byte[] networkMessageBytes = MiniConverter.IntToBytes((int) networkMessage);
            byte[] offset4 = networkMessageBytes;
            byte[] offset5 = MiniConverter.IntToBytes(networkMessageBytes.Length);
            byte[] sendBuffer = new byte[4 + 2 + 2 + 4 + 4 + (int) packet.Length];

            Array.Copy(offset1, 0, sendBuffer, 0, 4);
            Array.Copy(offset2, 0, sendBuffer, 4, 2);
            Array.Copy(offset3, 0, sendBuffer, 6, 2);
            Array.Copy(offset4, 0, sendBuffer, 8, 4);
            Array.Copy(offset5, 0, sendBuffer, 12, 4);
            Array.Copy(packet, 0, sendBuffer, 16, packet.Length);

            Debug.Log("发送消息 : " + (int) networkMessage + " 包长度 :" + packet.Length);
            lock (OutcomingMessageLock)
            {
                m_SendQueue.Enqueue(sendBuffer);
            }
        }

        private Dictionary<int, IEventInfo> m_msgMap = new Dictionary<int, IEventInfo>(); //消息集合

        public void OnMsg<T>(int msgkey, UnityAction<T> callbackFun)
        {
            if (m_msgMap.ContainsKey(msgkey))
            {
                (m_msgMap[msgkey] as EventInfo<T>).actions += callbackFun;
            }
            else
            {
                m_msgMap.Add(msgkey, new EventInfo<T>(callbackFun));
            }
        }

        public void OnRemoveMsg<T>(int msgkey, UnityAction<T> callbackFun)
        {
            if (m_msgMap.ContainsKey(msgkey))
            {
                (m_msgMap[msgkey] as EventInfo<T>).actions -= callbackFun;
            }
        }

        public void DispatchMsgData<T>(int msgID, T info)
        {
            if (m_msgMap.ContainsKey(msgID))
            {
                (m_msgMap[msgID] as EventInfo<T>).actions?.Invoke(info);
            }
            else
            {
                if (msgID != 100006)
                    Debug.LogWarning(" No msg regiter ! id :" + msgID);
            }
        }

        public void doSyncEntityID()
        {
            byte[] b = MiniConverter.IntToBytes(PlayerData.EntityID);
            byte[] sendBuffer = new byte[4];
            Array.Copy(b, 0, sendBuffer, 0, b.Length);
            SendMsg((MsgTile) ENetworkMessage.Net_SyncEntityID, sendBuffer);
        }

        public void OnSyncEntityID(object obj)
        {
            EventMgr.GetInstance().DispatchEvent(WSEvent.HandShake);
        }

        //发送心跳包
        public void OnHeartBeat(object obj)
        {
            byte[] b = MiniConverter.IntToBytes(PlayerData.EntityID);
            byte[] sendBuffer = new byte[4];
            Array.Copy(b, 0, sendBuffer, 0, b.Length);
            SendMsg((MsgTile) ENetworkMessage.Net_HeartBeat, sendBuffer);
        }

        //该用户已经在登录中
        public void OnNetHadConnect(object obj)
        {
            UICommon.GetInstance().ShowDialogYes("提示", "该用户正在登录中!");
        }

        public void OnClose()
        {
            UICommon.GetInstance().ShowDialogYesAndNo("提示", "服务器已经断开连接!",
                () => { SceneMgr.GetInstance().EnterLoginScene(); },
                () => { SceneMgr.GetInstance().EnterLoginScene(); },
                null,
                null);
        }

        public void CloseSocket()
        {
            if (m_Socket != null && m_Socket.Connected)
            {
                m_IsConnected = false;
                m_ReceiveQueue.Clear();
                m_ReceiveQueue.Clear();
                m_Socket?.Shutdown(SocketShutdown.Both);
                m_Socket?.Close();
                m_Socket?.Dispose();
                m_ReceiveStream?.Close();
                m_ReceiveStream?.Dispose();
                sendThread.Abort();
                receiveThread.Abort();
                sendCompletedAsync = true;
                receiveCompletedAsync = true;
                m_readWritePool.Clear();
            }
        }
    }
}
相关推荐
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i8 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
Leoysq17 小时前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui
_oP_i20 小时前
unity中 骨骼、纹理和材质关系
unity·游戏引擎·材质
Padid1 天前
Unity SRP学习笔记(二)
笔记·学习·unity·游戏引擎·图形渲染·着色器
Tp_jh1 天前
推荐一款非常好用的C/C++在线编译器
linux·c语言·c++·ide·单片机·unity·云原生
dangoxiba2 天前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十八集补充:制作空洞骑士独有的EventSystem和InputModule
游戏·unity·c#·游戏引擎·playmaker
无敌最俊朗@2 天前
unity3d————屏幕坐标,GUI坐标,世界坐标的基础注意点
开发语言·学习·unity·c#·游戏引擎
_oP_i2 天前
Unity 中使用 WebGL 构建并运行时使用的图片必须使用web服务器上的
前端·unity·webgl
司军礼2 天前
Unity自动打包——Shell交互
unity·游戏引擎·交互