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

    技术2022-07-11  87

    解决粘包和分包问题

    在发送的每条数据前加上该条数据的数据长度,这一长度用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; } /// <summary> /// 解析数据 /// </summary> 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解析成功。

    Processed: 0.012, SQL: 10