因此,解决问题的关键在于如何给每个数据包添加边界信息,基本上有以下三种常见解决办法
-
发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。【本次项目的解决方法】
-
发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
-
可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
使用第一种方法,写一个构造包的类,包括 包头(数据长度)和包尾(数据)
class EncodeTool{ // 构造包 包头+包尾 public static byte[] EncodePacket(byte[] data){ using(MemoryStream ms = new MemoryStream()){ using(BinaryWriter bw = new BinaryWriter(ms)){ // 1. 写入包头(数据长度) bw.Write(data.length); // 2. 写入包尾(数据) bw.Write(data); // 3. 拷贝 byte[] targetBuffer = new byte[ms.length]; Buffer.BlockCopy(ms.GetBuffer(),0,targetBuffer,0,(int)ms.Length); // 这里的ms.Length是长整型的,为了匹配形参,强制转换为int类型 return targetBuffer; // 4. 自动关闭流 // 5. 返回构建的数据包 } } } }
// 承接上面EncodeTool类 public static byte[] DecodePacket(ref List<byte> cache) { if (cache.Count < 4) { // cache的数据长度还不到一个int类型数据->包头也不够->没数据 return null; } using(MemoryStream ms = new MemoryStream()) { using (BinaryReader br = new BinaryReader(ms)) { int length = br.ReadInt32();// 刚好对应构建包中的包头部分 // 1. 计算缓冲区剩下的数据字节长度(即数据部分) int remainLength = (int)(ms.Length - ms.Position);// 这里的ms.position自行体会 if (remainLength < length) { // 如果剩下的数据长度小于读取到的数据长度,就说明这个缓冲区内并不存在一个完整的包(也就是数据>缓冲区的情况) return null; } // 至少包括一个完整的包 byte[] data = br.ReadBytes(length);// 读取length长度的数据(即解析的数据包中的数据)并存储至data字节数组 // 2. 更新数据缓存,将被读取的数据移除,继续读取下一个数据包 cache.Clear();// 这里就是为什么本方法形参前需要加ref关键字的原因 int remainLengthAgain = (int)(ms.Length - ms.Position);// 清空之后重新获取剩下的数据长度 cache.AddRange(br.ReadBytes(remainLengthAgain));// 读取处已解析数据包外的数据部分并转移至缓冲区 // 3. 返回解析的数据 return data; } } }
https://www.cnblogs.com/YuanShiRenY/p/TCPRelated.html