对于粘包和分包问题的解决方法

解决粘包和分包问题

在发送的每条数据前加上该条数据的数据长度,这一长度用int32来存放,始终占用4个字节

如果服务端收到的一条数据是粘包数据,它本身是由客户端多条数据组成。服务器解析的时候先读取前4个字节,可以从粘包数据中的获取第一条信息长度,通过这个长度值解析得到客户端的一条信息,紧接着读取下面的4个字节获取第二条信息的长度,以此类推。
同理,如果服务端收到的一条分包数据,同样先读取前4个字节获取信息长度,这条数据的长度肯定不是完整的信息,不足的那一部分由服务端收到的第二条数据的前半部分弥补

客户端数据拼接

对每一条客户端发送的信息都进行包装处理,即计算信息的长度存放在int型的前4个字节中,使得每条信息都变成数据长度+数据
Message.cs

using System;
using System.Linq;
using System.Text;

namespace TCPClient
{
    // 处理消息的拼接
    public class Message
    {
        // 字符串转换成字节数组,然后加上数据长度
        public static byte[] GetBytes(string data)
        {
            // 数据的长度就是dataBytes数组的大小
            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
            int dataLength = dataBytes.Length;
            // 转换成字节数组
            byte[] lengthBytes = BitConverter.GetBytes(dataLength);
            // 数据长度+数据 组拼成新数组 前四个字节存储数据长度,后面存储数据
            byte[] newBytes = lengthBytes.Concat(dataBytes).ToArray();
            return newBytes;
        }
    }
}

客户端发送数据

这里发送的200信息会被优化机制进行粘包处理,且数据包中的每一条信息都是经过Message加工过的数据。
TCPClient

using System;
using System.Net.Sockets;
using System.Net;
using System.Text;

namespace TCPClient
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            Socket clientSocket = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            // 客户端不需要绑定ip和端口 只需要和服务器端建立连接
            clientSocket.Connect(new IPEndPoint(IPAddress.Parse("10.8.215.46"), 4869));

            byte[] data = new byte[1024];
            int count = clientSocket.Receive(data);
            // 调用玩Receive后整个程序会暂停在这里,直到当它接收到服务器信息的时候
            string msg = Encoding.UTF8.GetString(data, 0, count);
            Console.WriteLine(msg);

            for (int i = 0; i < 100; i++)
            {
                // 每次传输数据之前会加上一个长度进行传输
                clientSocket.Send(Message.GetBytes(i.ToString()));
            }
        }
    }
}

服务端数据解析

Message.cs

using System;
using System.Text;

namespace TCPServer
{
    public class Message
    {
        // 从客户端读取到数据后存入message进行解析,另一方法解析从data中解析
        private byte[] data = new byte[1024]; // 让最大消息长度小于1024 
        private int dataLength = 0; // 从0开始存 数组里存了多少个字节的数据

        // 提供访问的方法
        public byte[] Date => data;
        public int StartIndex => dataLength;

        // 还剩余的空间
        public int RemainSize => data.Length - dataLength;

        // 读取到count个字节的数字,让索引加上count
        public void AddCount(int count)
        {
            dataLength += count;
        }

        /// 
        /// 解析数据
        /// 
        public void ReadMessage()
        {
            while (true)
            {
                // 小于4数据长度不完整,需要继续接收消息之后进行解析 
                if (dataLength <= 4)
                    return;
                // 先解析长度0-3的4个字节
                int length = BitConverter.ToInt32(data, 0); // 读取固定4字节
                // 读取剩余数据 大于等于时说明数据是完整的
                if (dataLength - 4 >= length)
                {
                    Console.WriteLine(dataLength+":"+length);
                    // 从4号索引开始读取数据
                    string str = Encoding.UTF8.GetString(data, 4, length);
                    Console.WriteLine("解析到一条数据:" + str);
                    // 把后面的数据前移,进行更新
                    Array.Copy(data, length + 4, data,
                        0, dataLength - length - 4);
                    // 更新信息长度 减去已经解析的数据
                    dataLength -= (length + 4);
                }
                else
                {
                    break; // 数据不完整,等待新的数据接收
                }
            }
        }
    }
}

服务端接收数据

using System;
using System.Net.Sockets;
using System.Net;
using System.Text;

// use class Socket and TCP
namespace TCPServer
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            StartServerAsync();
            Console.ReadKey();
        }

        // 异步 必须是静态 
        static void StartServerAsync()
        {
            Socket serverSocket = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Parse("10.8.215.46");
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 4869);
            serverSocket.Bind(ipEndPoint);
            // 开始监听端口 设置0表示不限制数量
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, serverSocket);
        }

        static Message msg = new Message();

        // 回调函数 接收到一个客户端连接的时候,要对客户端发起监听
        static void AcceptCallBack(IAsyncResult ar)
        {
            Socket serverSocket = ar.AsyncState as Socket;
            Socket clientSocket = serverSocket.EndAccept(ar);
            string msgStr = "Hello client!你好...";
            byte[] data = Encoding.UTF8.GetBytes(msgStr);
            clientSocket.Send(data);
            // 偏移从msg.StartIndex开始存,存取的最大数量设置数组的剩余空间msg.RemainSize,事件方法,
            clientSocket.BeginReceive(msg.Date, msg.StartIndex, msg.RemainSize, SocketFlags.None,
                ReceiveCallBack, clientSocket);

            // 接收完一个客户端之后,重新调用 继续处理下一个客户端连接
            serverSocket.BeginAccept(AcceptCallBack, serverSocket);
        }

        static void ReceiveCallBack(IAsyncResult ar)
        {
            Socket clientSocket = null;
            try
            {
                clientSocket = ar.AsyncState as Socket;
                int count = clientSocket.EndReceive(ar);
                if (count == 0)
                {
                    clientSocket.Close();
                    return;
                }

                // 读取到count字节以后更新starIndex
                msg.AddCount(count);

                // *数据解析(循环的解析)
                msg.ReadMessage();

                // 递归,继续执行服务端接收下一个客户端消息
                clientSocket.BeginReceive(msg.Date, msg.StartIndex, msg.RemainSize, SocketFlags.None,
                    ReceiveCallBack, clientSocket);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                // 只出异常时关闭连接
                if (clientSocket != null)
                {
                    clientSocket.Close();
                }
            }
        }
    }
}

测试结果

单个客户端向服务器发送200条数据,数据0~199解析成功。
对于粘包和分包问题的解决方法_第1张图片

你可能感兴趣的:(socket)