C# Unity's Socket connection server handles sticky packet subcontracting

1. What is a sticky packet;
multiple complete and incomplete messages are sent together and sent out. In order to solve the performance problem, TCP sticks the packet.

2. What is sub-packet:
The amount of sending is very large. If a message is sent multiple times, TCP will send it separately. If a packet is divided 10 times, the server will receive 10 times.

3. It is packaged and subpackaged at the transport layer, and we can only deal with it at the application layer.

4. How to solve the problem of sticking and subcontracting:

用封包和拆包解决
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[]组合在一起,再次进行这样的循环,保证数据的完整性和独立性。

The code is as follows
1 : Create a message class

    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;
        }
    }
}

Two: Create a MessageHandle to process the received message

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;
                        }
                    }
                }
            }
        }
    }

}

Three: Create a SocketClient to process the connection to the server, receive messages from the server, and send messages to the server

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();
            } 
        }

    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325543243&siteId=291194637