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();
            }
        }
    }
}
相关推荐
90后小陈老师9 小时前
Unity教学 项目2 2D闯关游戏
游戏·unity·游戏引擎
噗噗夹的TA之旅10 小时前
Unity Shader 学习20:URP LitForwardPass PBR 解析
学习·unity·游戏引擎·图形渲染·技术美术
nnsix10 小时前
Unity ReferenceFinder插件 多选资源查找bug解决
unity·游戏引擎·bug
gzroy11 小时前
Unity Shader Graph实现全息瞄准器
unity·游戏引擎
90后小陈老师14 小时前
Unity教学 基础介绍
unity·游戏引擎
90后小陈老师14 小时前
Unity教学 项目3 3D坦克大战
3d·unity·游戏引擎
秦奈16 小时前
Unity复习学习随笔(五):Unity基础
学习·unity·游戏引擎
nnsix17 小时前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
麷飞花18 小时前
unity3d scene窗口选中物体, 在 hierarchy高光显示
unity·editor·unity3d·u3d·hierarchy
ۓ明哲ڪ19 小时前
Unity功能——关闭脚本自动编译(Unity2021.3)
unity·游戏引擎