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();
            }
        }
    }
}
相关推荐
nnsix9 小时前
Unity PicoVR开发 实时预览Unity场景 在Pico设备中(串流)
unity·游戏引擎
一只一只15 小时前
Unity之UGUI Button按钮组件详细使用教程
unity·游戏引擎·ugui·button·ugui button
WarPigs18 小时前
Unity阴影
unity·游戏引擎
一只一只18 小时前
Unity之Invoke
unity·游戏引擎·invoke
tealcwu21 小时前
【Unity踩坑】Simulate Touch Input From Mouse or Pen 导致检测不到鼠标点击和滚轮
unity·计算机外设·游戏引擎
ThreePointsHeat1 天前
Unity WebGL打包后启动方法,部署本地服务器
unity·游戏引擎·webgl
迪普阳光开朗很健康1 天前
UnityScrcpy 可以让你在unity面板里玩手机的插件
unity·游戏引擎
陈言必行2 天前
Unity 之 设备性能分级与游戏画质设置与设备自动适配指南
游戏·unity·游戏引擎
CreasyChan2 天前
Unity DOTS技术栈详解
unity·c#·游戏引擎
在路上看风景2 天前
1.1 Unity资源生命周期管理与内存机制
unity