在发送的每条数据前加上该条数据的数据长度,这一长度用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; // 数据不完整,等待新的数据接收 } } } } }单个客户端向服务器发送200条数据,数据0~199解析成功。