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();
            }
        }
    }
}
相关推荐
mxwin14 小时前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F16 小时前
三层时间轮的实现
网络·unity·游戏引擎
咸鱼永不翻身17 小时前
Lua脚本事件检查工具
unity·lua·工具
leo__52019 小时前
单载波中继系统资源分配算法MATLAB仿真程序
算法·matlab·unity
努力长头发的程序猿20 小时前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin20 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发20 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安21 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
魔士于安21 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin21 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader