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();
            }
        }
    }
}
相关推荐
牙膏上的小苏打233316 小时前
Unity Surround开关后导致获取主显示器分辨率错误
unity·主屏幕
Unity大海18 小时前
诠视科技Unity SDK开发环境配置、项目设置、apk打包。
科技·unity·游戏引擎
浅陌sss1 天前
Unity中 粒子系统使用整理(一)
unity·游戏引擎
维度攻城狮1 天前
实现在Unity3D中仿真汽车,而且还能使用ros2控制
python·unity·docker·汽车·ros2·rviz2
为你写首诗ge1 天前
【Unity网络编程知识】FTP学习
网络·unity
神码编程1 天前
【Unity】 HTFramework框架(六十四)SaveDataRuntime运行时保存组件参数、预制体
unity·编辑器·游戏引擎
菲fay1 天前
Unity 单例模式写法
unity·单例模式
火一线1 天前
【Framework-Client系列】UIGenerate介绍
游戏·unity
ZKY_242 天前
【工具】Json在线解析工具
unity·json
ZKY_242 天前
【Unity】处理文字显示不全的问题
unity·游戏引擎