Unity TCPSocket粘包和拆包问题

问题产生

一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题。

下面可以看一张图,是客户端向服务端发送包:

1. 第一种情况,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况。
2. 第二种情况,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包。
3. 第三种情况,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到达了服务端,这种情况就产生了拆包。
由于网络的复杂性,可能数据会被分离成N多个复杂的拆包/粘包的情况,所以在做TCP服务器的时候就需要首先解决拆包/粘包的问题。

 

TCP粘包和拆包产生的原因


1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
2. 进行MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度
3. 以太网的payload大于MTU进行IP分片。MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。如果IP层有一个数据包要传,而且数据的长度比链路层的MTU大,那么IP层就会进行分片,把数据包分成若干片,让每一片都不超过MTU。注意,IP分片可以发生在原始发送端主机上,也可以发生在中间路由器上。

 

TCP粘包和拆包的解决策略


1. 消息定长。例如100字节。
2. 在包尾部增加特殊字符进行分割。
3. 将消息分为消息头和消息尾。(最常用)
 

先把我看了大神的代码后,写的代码放出来,方便以后不懂得时候回来看:

服务器端(在Unity中实现):

using UnityEngine;
using System.Collections;
//引入库
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System;
using LuaInterface;
using System.IO;

public class TcpServer : MonoBehaviour
{
    //以下预设都是私有的成员
    Socket serverSocket; //伺服器端socket
    Socket clientSocket; //客户端socket
    IPEndPoint ipEnd; //侦听埠
    string recvStr; //接收的字串
    string sendStr; //传送的字串
    byte[] recvData = new byte[1024]; //接收的资料,必须为位元组
    byte[] sendData = new byte[1024]; //传送的资料,必须为位元组
    int recvLen; //接收的资料长度
    Thread connectThread; //连线执行绪

    private int contentSize = 0;

    //初始化
    void InitSocket()
    {
        //定义侦听埠,侦听任何IP
        ipEnd = new IPEndPoint(IPAddress.Any, 5566);
        //定义套接字型别,在主执行绪中定义
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //连线
        serverSocket.Bind(ipEnd);
        //开始侦听,最大10个连线
        serverSocket.Listen(10);



        //开启一个执行绪连线,必须的,否则主执行绪卡死
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }

    //连线
    void SocketConnet()
    {
        if (clientSocket != null)
            clientSocket.Close();
        //控制台输出侦听状态
        print("Waiting for a client");
        //一旦接受连线,建立一个客户端
        clientSocket = serverSocket.Accept();
        //获取客户端的IP和埠
        IPEndPoint ipEndClient = (IPEndPoint)clientSocket.RemoteEndPoint;
        //输出客户端的IP和埠
        print("Connect with " + ipEndClient.Address.ToString() + ":" + ipEndClient.Port.ToString());
        //连线成功则传送资料
        sendStr = "Welcome to my server";
        SocketSend(sendStr);
    }

    void SocketSend(string sendStr)
    {
        //清空传送快取
        sendData = new byte[1024];
        //资料型别转换
        sendData = Encoding.ASCII.GetBytes(sendStr);
        //传送
        clientSocket.Send(sendData, sendData.Length, SocketFlags.None);
    }

    private NetworkStream stream;
    //伺服器接收
    void SocketReceive()
    {
        //连线
        SocketConnet();
        //进入接收回圈
        while (true)
        {
            //对data清零
            recvData = new byte[1024];
            //获取收到的资料的长度
            recvLen = clientSocket.Receive(recvData);
            contentSize += recvLen;

            while (true)
            {
                //判断接收到的字符长度,如果连包头的长度都不够就不进行了
                if (contentSize <= 4)
                {
                    return;
                }
                //得到包体的长度
                int receiveCount = BitConverter.ToInt32(recvData, 0);
                //接收的数据不到一个完整的数据
                if (contentSize - 4 < receiveCount)
                {
                    return;
                }
                //去除包头后的数据
                string receiveStr = Encoding.UTF8.GetString(recvData, 4, receiveCount);
                Debug.Log(receiveStr);
                //将剩余数据存储到缓冲区
                Array.Copy(recvData, 4 + receiveCount, recvData, 0, contentSize - 4 - receiveCount);

                contentSize = contentSize - 4 - receiveCount;
            }

            ////Debug.Log("获取资源的长度: " + recvLen);
            //////如果收到的资料长度为0,则重连并进入下一个回圈
            //if (recvLen == 0)
            //{
            //    SocketConnet();
            //    continue;
            //}
            ////输出接收到的资料
            //recvStr = Encoding.ASCII.GetString(recvData, 4, recvLen-4);
            //Debug.Log("接收到的所有数据: " + recvStr);
        }
    }


    //连线关闭
    void SocketQuit()
    {
        //先关闭客户端
        if (clientSocket != null)
            clientSocket.Close();
        //再关闭执行绪
        if (connectThread != null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //关闭服务器
        serverSocket.Close();
        print("diconnect");
    }

    // Use this for initialization
    void Start()
    {
        InitSocket(); //在这里初始化server
    }

    void OnApplicationQuit()
    {
        SocketQuit();
    }
}

 

客户端:

using UnityEngine;
using System.Collections;
//引入库
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.IO;
using System;

public class TcpClient : MonoBehaviour
{
    string editString = "hello wolrd"; //编辑框文字

    Socket serverSocket; //伺服器端socket
    IPAddress ip; //主机ip
    IPEndPoint ipEnd;
    string recvStr; //接收的字串
    string sendStr; //传送的字串
    byte[] recvData = new byte[1024]; //接收的资料,必须为位元组
    byte[] sendData = new byte[1024]; //传送的资料,必须为位元组
    int recvLen; //接收的资料长度
    Thread connectThread; //连线执行绪

    //初始化
    void InitSocket()
    {
        //定义伺服器的IP和埠,埠与伺服器对应
        ip = IPAddress.Parse("127.0.0.1"); //可以是区域网或网际网路ip,此处是本机
        ipEnd = new IPEndPoint(ip, 5566);


        //开启一个执行绪连线,必须的,否则主执行绪卡死
        connectThread = new Thread(new ThreadStart(SocketReceive));
        connectThread.Start();
    }

    void SocketConnet()
    {
        if (serverSocket != null)
            serverSocket.Close();
        //定义套接字型别,必须在子执行绪中定义
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        print("ready to connect");
        //连线
        serverSocket.Connect(ipEnd);

        //输出初次连线收到的字串
        recvLen = serverSocket.Receive(recvData);
        recvStr = Encoding.ASCII.GetString(recvData, 0, recvLen);
        print(recvStr);
    }

    void SocketSend(string sendStr)
    {
        //清空传送快取
        sendData = new byte[1024];
        //资料型别转换
        sendData = Encoding.ASCII.GetBytes(sendStr);
        sendData = BuildDataPackage(sendData);
        Debug.Log("输出的字节总长度:" + sendData.Length);
        Debug.Log("字节体的长度 : " + BitConverter.ToInt32(sendData, 0));
        Debug.Log("字节体: " + Encoding.ASCII.GetString(sendData ,4, sendData.Length-4));
        //传送
        serverSocket.Send(sendData, sendData.Length, SocketFlags.None);
    }

    /// <summary>
    /// 构建数据
    /// </summary>
    /// <param name="data"></param>
    /// <param name="dataLength"></param>
    public byte[] BuildDataPackage(byte[] data)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            using (BinaryWriter bw = new BinaryWriter(ms))
            {
                bw.Write(data.Length);
                bw.Write(data);

                byte[] byteArray = new byte[(int)ms.Length];
                Buffer.BlockCopy(ms.GetBuffer(), 0, byteArray, 0, (int)ms.Length);
                return byteArray;
            }
        }
    }

    void SocketReceive()
    {
        SocketConnet();
        //不断接收伺服器发来的资料
        while (true)
        {
            recvData = new byte[1024];
            recvLen = serverSocket.Receive(recvData);
            if (recvLen == 0)
            {
                SocketConnet();
                continue;
            }
            recvStr = Encoding.ASCII.GetString(recvData, 0, recvLen);
            print(recvStr);
        }
    }

    void SocketQuit()
    {
        //关闭执行绪
        if (connectThread != null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //最后关闭伺服器
        if (serverSocket != null)
            serverSocket.Close();
        print("diconnect");
    }

    // Use this for initialization
    void Start()
    {
        InitSocket();
    }

    void OnGUI()
    {
        editString = GUI.TextField(new Rect(10, 10, 100, 20), editString);
        if (GUI.Button(new Rect(10, 30, 60, 20), "send"))
        {
            SocketSend(editString);
        }
    }

    // Update is called once per frame
    void Update()
    {

    }

    //程式退出则关闭连线
    void OnApplicationQuit()
    {
        SocketQuit();
    }
}

 

-----------------------下面是大神的脚本用来加深学习--------------------------------

  class Program
    {
        static void Main(string[] args)
        {
            ///客户端代码
            Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.102"), 3344);
            clientSocket.Connect(ipEndPoint);

            byte[] receiveBuffer = new byte[1024];
            int count = clientSocket.Receive(receiveBuffer);

            string msg = Encoding.UTF8.GetString(receiveBuffer, 0, count);
            Console.WriteLine("接收到服务端的消息:" + msg);
            for (int i = 0; i < 100; i++) ///客户端启动向服务端发送250条数据
            {
                clientSocket.Send(SendMsg(i.ToString()));
            }
            Console.ReadKey();
        }

        /// <summary>
        /// 构造发送数据
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static byte[] SendMsg(string msg)
        {
            int length = msg.Length;
            //构造表头数据,固定4个字节的长度,表示内容的长度
            byte[] headerBytes = BitConverter.GetBytes(length);
            //构造内容
            byte[] bodyBytes = Encoding.UTF8.GetBytes(msg);
            byte[] tempBytes = new byte[headerBytes.Length + bodyBytes.Length];
            ///拷贝到同一个byte[]数组中,发送出去..
            Buffer.BlockCopy(headerBytes, 0, tempBytes, 0, headerBytes.Length);
            Buffer.BlockCopy(bodyBytes, 0, tempBytes, headerBytes.Length, bodyBytes.Length);
            return tempBytes;
        }
    }

/////服务端代码

    class Program
    {
        static void Main(string[] args)
        {
            ///服务端代码
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("192.168.1.102");
            IPEndPoint ipEndPoint = new IPEndPoint(ip, 3344);

            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);//开启监听

            Console.WriteLine("服务器启动");
            //开始异步接收客户端
            serverSocket.BeginAccept(AcceptAsyncCallBack, serverSocket);
            Console.ReadKey();
        }
        /// <summary>
        /// 异步等待客户端回调方法
        /// </summary>
        /// <param name="ar"></param>
        private static void AcceptAsyncCallBack(IAsyncResult ar)
        {

            Socket serverSocket = ar.AsyncState as Socket;//传递过来的参数
            Socket clientSokcet = serverSocket.EndAccept(ar); //一个客户端连接过来了

            string msg = ":Hello client! 你好......";
            byte[] dataBytes = Encoding.UTF8.GetBytes(msg);  //网络连接收发数据,只能发送byte[] 字节数组
            clientSokcet.Send(dataBytes);
            ///messageHandle.DataBuffer缓存区,messageHandle.ContentSize(缓存区中已经存在的内容长度开始存)
            ///  messageHandle.remainSize 缓存区中剩余可以存储的空间
            clientSokcet.BeginReceive(messageHandle.DataBuffer, messageHandle.ContentSize, messageHandle.remainSize, SocketFlags.None, ReceiveCallBack, clientSokcet); //开始异步接收数据


            serverSocket.BeginAccept(AcceptAsyncCallBack, serverSocket);//循环等待客户端接收....
        }

        static MessageHandle messageHandle = new MessageHandle();
        /// <summary>
        /// 异步接收数据回调方法
        /// </summary>
        /// <param name="ar"></param>
        private static void ReceiveCallBack(IAsyncResult ar)
        {
            Socket clientSocket = null;
            try
            {
                clientSocket = ar.AsyncState as Socket;
                int count = clientSocket.EndReceive(ar);    //接收到的数据量
                if (count == 0) ///说明客户端已经已经断开连接了
                {
                    if (clientSocket != null)
                    {
                        clientSocket.Close();
                    }
                    return;
                }
                //j解析数据(把新接收的数据传入)
                messageHandle.ReadMessage(count);
                //开始异步接收数据
                clientSocket.BeginReceive(messageHandle.DataBuffer, messageHandle.ContentSize, messageHandle.remainSize, SocketFlags.None, ReceiveCallBack, clientSocket); 

            }
            catch (Exception e)///说明客户端已经已经断开连接了,异常断开
            {
                Console.WriteLine(e);
                if (clientSocket != null)
                {
                    clientSocket.Close();
                }
            }
        }
    }
  public class MessageHandle
    {
        //表头的数据长度为4个个字节,表示后面的数据的长度
        //保证能够每次接收发送的消息小于1024bit大小,否则无法完整接收整条数据
        private byte[] dataBuffer = new byte[1024];
        //从dataBuffer已经存了多少个字节数据
        private int contentSize = 0;

        public int ContentSize {
            get { return contentSize; }
        }
        /// <summary>
        /// 剩余多少存储空间
        /// </summary>
        public int remainSize {
            get { return dataBuffer.Length - contentSize; }
        }

        public byte[] DataBuffer {
            get { return dataBuffer; }
        }

        /// <summary>
        /// 解析数据 ,count 新读取到的数据长度
        /// </summary>
        public void ReadMessage(int count)
        {
            contentSize += count;
            //用while表示缓存区,可能有多条数据
            while (true)
            {
                //缓存区小于4个字节,表示连表头都无法解析
                if (contentSize <= 4) return;
                //读取四个字节数据,代表这条数据的内容长度(不包括表头的4个数据)
                int receiveCount = BitConverter.ToInt32(dataBuffer, 0);
                //缓存区中的数据,不够解析一条完整的数据
                if (contentSize - 4 < receiveCount) return;

                //2、解析数据
                //从除去表头4个字节开始解析内容,解析的数据长度为(表头数据表示的长度)
                string receiveStr = Encoding.UTF8.GetString(dataBuffer, 4, receiveCount);

                Console.WriteLine("接收的客户端数据:" + receiveStr);
                //把剩余的数据Copy到缓存区头部位置
                Array.Copy(dataBuffer, 4 + receiveCount, dataBuffer, 0, contentSize - 4 - receiveCount);

                contentSize = contentSize - 4 - receiveCount;
            }
        }

        /// <summary>
        /// 构造发送数据
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public byte[] SendMsg(string msg)
        {
            int length = msg.Length;
            //构造表头数据,固定4个字节的长度,表示内容的长度
            byte[] headerBytes = BitConverter.GetBytes(length);
            //构造内容
            byte[] bodyBytes = Encoding.UTF8.GetBytes(msg);
            byte[] tempBytes = new byte[headerBytes.Length + bodyBytes.Length];
            ///拷贝到同一个byte[]数组中,发送出去..
            Buffer.BlockCopy(headerBytes, 0, tempBytes, 0, headerBytes.Length);
            Buffer.BlockCopy(bodyBytes, 0, tempBytes, headerBytes.Length, bodyBytes.Length);
            return tempBytes;
        }

    }

参考:https://blog.csdn.net/qq_33537945/article/details/79180502

https://blog.csdn.net/yang854426171/article/details/88764319?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-3

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章