C# Unity的Socket连接服务器处理粘包分包

1.什么是黏包;
将多条完整的活不完整的消息黏在一起发送发送出去,TCP为解决性能问题,所以他进行黏包。

2.什么是分包:
发送的数量量很大,一条消息多次发送,TCP就会分开发送,一个包被分开10次,服务器就会recive10次

3.在传输层就被粘包和分包了,我们只能在应用层处理。

4.如何解决粘包和分包问题:

用封包和拆包解决
1.封包:
    定义消息协议类,
    由5大属性组成,分别是:

    【(①=1个int)(②=1个int)(③=1个int)(④=1个byte[])(⑤=1个byte[])】

    解释一下这个【消息协议类】:

    (①=1个int):这个属性占4个字节,可以放一个0到2147483647的整数,我称作1号标志;

    (②=1个int):这个属性占1个字节,可以放一个0到2147483647的整数,我称作2号标志。那么1号标志和2号标志就有多达2147483647×2147483647个组合,我们用它来自定义这个消息的标志,比如,0-0表示登录请求消息,1-1表示物理攻击,1-2表示魔法攻击,3-3表示坐标移动;

    (③=1个int):这个属性占4个字节,可以放一个0到2147483647的整数,它表示(④=1个byte[])的长度;

    (④=1个byte[]):这个属性存着你要发送的全部消息体字节,所以,你的消息体需要被转化为字节数组才可存放进去;

    (⑤=1个byte[]):这个属性存着【多余的消息体字节】。那么问题来了,什么是【多余的消息体字节】

    再解释一下这个【消息协议类】具体怎么用。

    1,【消息发送方】先定义【消息协议类】①②(一级协议和二级协议)属性,也就是随便写2个数;

    2,【消息发送方】再将消息“装入”【消息协议类】的④属性,那么③属性就有了;

    3,【消息发送方】将封装好的【消息协议类】转为byte[],发送给【消息接收方】,我们把这道工序称作【封包】;

2.拆包
    4,【消息接收方】接收到【消息发送方】发来的byte[]时,先判断这个byte[]长度是否大于6,即是否大于①属性+②属性+③属性的长度和,如果byte[]长度小于 【消息接收方】就不做处理循环继续接收;

    5,【消息接收方】接收到【消息发送方】发来的byte[]长度大于等于6了(包含一个完整的消息)!则将byte[]还原为【消息协议类】,为了区别,我们暂时把它为【新消息协议类】;

    6,循环判断【消息发送方】发来的byte[]长度减去6之后的值是否大于等于【新消息协议类】的③的值。这个可以理解为byte[]是否为一个完整的【消息协议类】,如果是就把【新消息协议类】的④属性拆出来,就得到了一个刚刚好完整的消息,不多也不少。那么,我们就把这道工序称作【拆包】;

    7,相信你已经反应过来⑤这个【多余的消息体字节】是干嘛用的了。上一步当中,如果byte[]信息刚好是一个完整长度自然用不到⑤了,但是在网络传输中byte[]自然不会永远那么刚好了,所以当byte[]长度大于一个完整消息时,就把多于的byte放入⑤当中,和下次新接收的byte[]组合在一起,再次进行这样的循环,保证数据的完整性和独立性。

代码如下
一:创建一个消息类

    using UnityEngine;
using System.Collections;
using System.IO;
namespace LuaFramework.NetWork
{
    /// <summary>
    /// 消息协议类
    /// </summary>
    public class MessageProtocol
    {
        public int oneNumber;//一级协议
        public int twoNumber;//二级协议
        public int length;//实际数据长度
        public byte[] buffer=new byte[] { };//实际消息数据
        public byte[] duoYuBytes=new byte[] { };//多余数据字节数组
        #region 构造
        public MessageProtocol() { }
        public MessageProtocol(int oneNumber, int twoNumber, int length, byte[] buffer)
        {
            this.oneNumber = oneNumber;
            this.twoNumber = twoNumber;
            this.buffer = buffer;
            this.length = this.buffer.Length;
        }
        #endregion

        /// <summary>
        /// 将消息协议对象转化字节数组
        /// </summary>
        /// <returns></returns>
        public byte[] ToBytes()
        {
            byte[] bytes;//自定义字节数组,用以装载消息协议
            using (MemoryStream memorySteam = new MemoryStream())//创建内存流
            {
                BinaryWriter binaryWriter = new BinaryWriter(memorySteam);//以二进制写入器往这个流里写内容
                binaryWriter.Write(this.oneNumber);//写入协议一级标志,占4个字节
                binaryWriter.Write(this.twoNumber);//写入协议二级标志,占4个字节
                binaryWriter.Write(this.length);//写入消息的长度,占4个字节
                if (this.length > 0)
                {
                    binaryWriter.Write(this.buffer);//写入消息实际内容
                }
                bytes = memorySteam.ToArray();
                binaryWriter.Close();
            }
            return bytes;
        }

        /// <summary>
        /// 从字节数组得到消息类对象
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static MessageProtocol FromBytes(byte[] data)
        {
            int dataLength = data.Length;
            MessageProtocol protocol = new MessageProtocol();
            using (MemoryStream memoryStream = new MemoryStream(data))//将字节数组填充至内存流
            {
                BinaryReader binaryReader = new BinaryReader(memoryStream);//以二进制读取器读取该流内容
                protocol.oneNumber = binaryReader.ReadInt32();//读取一级协议,占4字节
                protocol.twoNumber = binaryReader.ReadInt32();//读取二级协议,占4字节
                protocol.length = binaryReader.ReadInt32();//读取数据的长度,占4字节
                //如果【进来的Bytes长度】大于【一个完整的MessageXieYi长度】
                if (dataLength - 12 > protocol.length)
                {
                    protocol.buffer = binaryReader.ReadBytes(protocol.length);//读取实际消息的内容,从第13个字节开始读取,长度是消息的场地
                    protocol.duoYuBytes = binaryReader.ReadBytes(dataLength - 12 - protocol.length);//读取多余字节的数据
                }
                //如果【进来的Bytes长度】等于于【一个完整的MessageXieYi长度】
                if (dataLength - 12 == protocol.length)
                {
                    protocol.buffer = binaryReader.ReadBytes(protocol.length);
                }
                binaryReader.Close();
            }
            return protocol;
        }

        /// <summary>
        /// 按照先后顺序合并2个字节数组,并返回合并后的字节数组
        /// </summary>
        /// <param name="firstBytes">第一个字节数组</param>
        /// <param name="firstIndex">第一个字节数组的开始截取索引</param>
        /// <param name="firstLength">第一个字节数组的截取长度</param>
        /// <param name="secondBytes">第二个字节数组</param>
        /// <param name="secondIndex">第二个字节数组的开始截取索引</param>
        /// <param name="secondLength">第二个字节数组的截取长度</param>
        /// <returns></returns>
        public static byte[] CombineBytes(byte[] firstBytes, int firstIndex, int firstLength, byte[] secondBytes, int secondIndex, int secondLength)
        {
            byte[] buffer;
            using (MemoryStream memoryStream = new MemoryStream())//创建内存流
            {
                BinaryWriter binaryWriter = new BinaryWriter(memoryStream);//创建二进制写入器,往流中写入数据
                binaryWriter.Write(firstBytes, firstIndex, firstLength);//写入第一个字节数组
                binaryWriter.Write(secondBytes, secondIndex, secondLength);//写入第二个字节数组
                buffer = memoryStream.ToArray();
                binaryWriter.Close();
            }
            return buffer;
        }
    }
}

二:创建MessageHandle处理接收到的消息

using UnityEngine;
using System.Collections;
using System;
namespace LuaFramework.NetWork
{
    public class MessageHandle
    {
        public byte[] buffer = new byte[] { };//数据动态缓存区

        public MessageHandle() { }

        /// <summary>
        /// 处理接收的数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="count"></param>
        public void HandleMessage(byte[] data, int count)
        {
            buffer = MessageProtocol.CombineBytes(buffer, 0, buffer.Length, data, 0, data.Length);
            while (true)
            {
                MessageProtocol protocol = MessageProtocol.FromBytes(data);
                if (buffer.Length < 12)//接收的数据不到12个字节不处理
                {
                    return;
                }
                else
                {
                    //消息对象的包头
                    protocol = MessageProtocol.FromBytes(buffer);
                    int firstFlag = protocol.oneNumber;
                    int secondFlag = protocol.twoNumber;
                    int msgContentLength = protocol.length;
                    while (buffer.Length - 12 >= msgContentLength)
                    {
                        protocol = null;
                        protocol = MessageProtocol.FromBytes(buffer);
                        //取出消息内容,派发消息
                        Debug.Log(BitConverter.ToString(protocol.buffer));
                        //再讲多余的数据重新复制给动态数组,此时的动态数组只包含多余的字节
                        buffer = protocol.duoYuBytes;
                        if (buffer.Length >= 12)
                        {
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        }
    }

}

三:创建SocketClient处理连接服务器,接收服务器的消息,向服务器发送消息

using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System;
using System.Threading;
namespace LuaFramework.NetWork
{
    public class SocketClient 
    {
        #region 单利
        private static SocketClient instance = null;
        public static SocketClient Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new SocketClient();
                }
                return instance;
            }
        } 
        #endregion

        public Socket client;
        private Thread thread;
        private MessageHandle messageHandle;
        private string ipAdress;
        private int port;
        public bool isConnect = false;
        private byte[] buffer;//接收数据的缓存区
        /// <summary>
        /// 连接服务器外部接口
        /// </summary>
        /// <param name="ipAdress"></param>
        /// <param name="port"></param>
        public void Init(string ipAdress, int port)
        {
            client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            messageHandle = new MessageHandle();
            this.ipAdress = ipAdress;
            this.port = port;
            thread = new Thread(ConnectServer);
            thread.Start();
        }

        #region 连接服务器内部
        private void ConnectServer()
        {
            client = null;
            try
            {
                IPAddress[] address = Dns.GetHostAddresses(ipAdress);
                if (address.Length == 0)
                {
                    Debug.LogError("host invalid");
                    return;
                }
                if (address[0].AddressFamily == AddressFamily.InterNetworkV6)
                {
                    client = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
                }
                else
                {
                    client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                }
                client.SendTimeout = 1000;//设置发送超时时间,超时断开连接
                client.ReceiveTimeout = 1000;//设置接收超时时间
                client.NoDelay = true;
                client.BeginConnect(address, port, ConnectCallBack, client);//开始异步连接服务器
            }
            catch (Exception e)
            {
                Debug.Log("连接失败,断开连接");
                Close();
            }
        }
        private void ConnectCallBack(IAsyncResult ar)
        {
            try
            {
                client.EndConnect(ar);//连接服务器成功
                isConnect = true;
                Receive();//开始读取数据
            }
            catch (Exception e)
            {
                Close();
            }
        } 
        #endregion

        #region 接收数据
        private void Receive()
        {
            try
            {
                buffer = new byte[10240];
                client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallBack, client);
            }
            catch (Exception e)
            {
                Close();
            }
        }
        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                int count = client.EndReceive(ar);
                if (count == 0)
                {
                    Close();
                    return;
                }
                messageHandle.HandleMessage(buffer, count);//处理数据
                Receive();//再次接收数据
            }
            catch (Exception e)
            {
                Close();
            }
        }
        #endregion

        #region 发送数据
        public void Send(byte[] data)
        {
            if (client != null && client.Connected)
            {
                Debug.Log("向服务器发送的字节数BeginSend" + data.Length);
                client.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallBack, client);
            }
            else
            {
                Close();
            }
        }
        private void SendCallBack(IAsyncResult ar)
        {
            try
            {
                int count = client.EndSend(ar);
                Debug.Log("向服务器发送的字节数EndSend" + count);
            }
            catch (Exception e)
            {
                Close();
            }
        } 
        #endregion

        /// <summary>
        /// 关闭连接
        /// </summary>
        public void Close()
        {
            //一旦断开连接 走重新登录
            if (thread != null)
            {
                thread.Abort();
                thread = null;
            }
            if (client != null)
            {
                client.Close();
                isConnect = false;
                client = null;
            }
        }

        /// <summary>
        /// 判断是否处于连接
        /// </summary>
        public void IsConnected()
        {
            if (client.Connected == false)
            {
                Close();
            } 
        }

    }
}

猜你喜欢

转载自blog.csdn.net/baidu_39447417/article/details/79840922