BytesIO | C# 优雅处理通信中的粘包和断包(一)—— 处理固定长的的协议(完整源码+视频教程)

8 篇文章 67 订阅

C# 优雅处理通信中的粘包和断包(一) 处理固定长的的协议

在这里插入图片描述


前言

今天我们讲一下怎么用BytesIO里面的解包器优雅的解析带有协议的数据包。

什么是协议?

比如说我收到了一包数据,然后我去处理它。在我处理的同时呢,我又收到了两包数据。当我处理完手中的这一包数据再回过头来看我的缓存区,哎!我发现两包数据它们粘在一起了。那我应该怎么办呢?当然是要把它们拆开,而我拆开它们的依据就是——协议

有的协议呢它是一个固定长度的,比如我们说有一种协议,它的数据包长度固定就是十个字节。也就是说每十个字节我都可以认为它是一个完整的数据包。
有的协议呢它也没有说固定是多长,它的长度是在数据包的第n位注明的。从数据包的头部开始读取,读到长度位,它告诉你后续还有20个字节。
还有的协议它是以一个固定的符号作为分割,比如说回车、换行符这种。我也不知道我前面有多长,但是一遇特定的结束符号就知道这一包数据结束了。

这就是协议的作用。

为什么要用解包器呢?

因为在发送数据和接收数据的过程中有可能会发生粘包和断包这两种情况。

什么是粘包呢?

就如上文所说的那样,我在处理一个数据包的时候,多个数据包堆叠在了缓存区中,这样就发生了粘包。

什么是断包呢?

断包是为什么呢?对于发送端的种种原因,他一句话没说完,他给你来个大喘气,只说了前半部分,但是你通过前半部分呢已经知道他的话还没有说完,你就在那耐心的等他,等他…这就是遇到了断包。
遇到断包你就需要把前半部分存起来,耐心的等后半部分消息传来把前后数据拼接完整才认为这是一个完整的数据包。然后再进行处理。


视频教程

【女朋友都能学会】C# 协议解包器(优雅处理粘包断包)


源代码

通信协议格式

教学视频中展示出的协议格式草图(群友小伙伴提供):
在这里插入图片描述
针对此协议格式完成的数据包代码如下:

    /// <summary>
    /// 数据协议
    /// </summary>
    public class SimpleData : Response, IRequest
    {
        /// <summary>
        /// 协议起始标记
        /// </summary>
        public static readonly byte[] StartMark = new byte[] { 0xDD, 0xEE };

        /// <summary>
        /// 功能码
        /// </summary>
        public FunctionCode Code { get; }

        /// <summary>
        /// 方位角度
        /// </summary>
        public short Heading { get; }

        /// <summary>
        /// 俯仰角度
        /// </summary>
        public short Tilt { get; }

        public SimpleData(IEnumerable<byte> bytes) : base(bytes)
        {
            var data = bytes.ToArray();
            if (bytes.Count() != 8)
            {
                throw new ArgumentException("长度应为8位");
            }
            if (!bytes.StartWith(StartMark))
            {
                throw new ArgumentException("协议头错误");
            }
            if (CheckSum(bytes.Skip(2).Take(5)) != bytes.Last())
            {
                throw new ArgumentException("校验失败");
            }
            Code = (FunctionCode)data[2];
            Heading = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 3));
            Tilt = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 5));
        }

        public SimpleData(FunctionCode code, short heading, short tilt) : base(null)
        {
            Code = code;
            Heading = heading;
            Tilt = tilt;
        }

        public byte[] GetBytes()
        {
            List<byte> bytes = new List<byte>();
            bytes.AddRange(StartMark);
            bytes.Add((byte)Code);
            bytes.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Heading)));
            bytes.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Tilt)));
            byte checkSum = CheckSum(bytes.Skip(2));
            bytes.Add(checkSum);
            return bytes.ToArray();
        }

        private byte CheckSum(IEnumerable<byte> bytes)
        {
            byte checksum = 0x00;
            foreach (byte bt in bytes)
            {
                checksum ^= bt;
            }
            return checksum;
        }

        /// <summary>
        /// 功能码
        /// </summary>
        public enum FunctionCode : byte
        {
            /// <summary>
            /// 停止
            /// </summary>
            Stop = 0,
            /// <summary>
            /// 定点
            /// </summary>
            FixedPoint = 1,
            /// <summary>
            /// 扇扫
            /// </summary>
            Scan = 2,
        }
    }

解包器的实现

由于协议草稿中展示的协议格式是一种非常简单的固定长度协议,所以在实现计算数据包长度的方法时只需要返回固定的8位即可。

    /// <summary>
    /// 固定长度的协议解包器
    /// 数据包长度固定,实现非常简单;
    /// 重写CalculatePacketLength方法时返回固定长度值即可
    /// </summary>
    public class SimpleDataUnpacker : Unpacker
    {
        public SimpleDataUnpacker()
        {
            StartMark = SimpleData.StartMark;
        }

        protected override int CalculatePacketLength(IEnumerable<byte> bytes) => 8;
    }

运行结果

模拟了断包和粘包数据的接收,在视频中可以看到运行结果。
在这里插入图片描述


相关文章

C# 优雅处理通信中的粘包和断包(一) 处理固定长的的协议
C# 优雅处理通信中的粘包和断包(二) 处理非固定长度的协议
C# 优雅处理通信中的粘包和断包(三) 处理使用结束符的协议

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当客户端发送的数据度大于 TcpListener 接收缓冲区的大小时,就会发生问题,这时需要服务器端进行处理。下面是一个可以处理C# TcpListener 服务器示例代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; class TcpServer { static void Main(string[] args) { try { // 创建一个 TcpListener 对象 TcpListener listener = new TcpListener(IPAddress.Any, 8080); // 开始监听 listener.Start(); Console.WriteLine("Waiting for connection..."); while (true) { // 接受客户端连接 TcpClient client = listener.AcceptTcpClient(); Console.WriteLine("Client connected!"); // 获取客户端的网络流 NetworkStream stream = client.GetStream(); // 接收客户端发送的数据 byte[] buffer = new byte[1024]; int totalLength = 0; int bytesRead = 0; while (true) { // 读取数据 bytesRead = stream.Read(buffer, totalLength, buffer.Length - totalLength); totalLength += bytesRead; // 判数据是否接收完毕 if (bytesRead == 0) { break; } else if (stream.DataAvailable) { continue; } else if (totalLength >= 4) { int dataLength = BitConverter.ToInt32(buffer, 0); if (totalLength - 4 == dataLength) { // 接收到一个完整的数据 string data = Encoding.ASCII.GetString(buffer, 4, dataLength); Console.WriteLine("Received: {0}", data); // 发送响应数据给客户端 byte[] response = Encoding.ASCII.GetBytes("Hello from server!"); stream.Write(response, 0, response.Length); break; } else if (totalLength - 4 > dataLength) { // 接收到多个数据 int offset = 4; while (totalLength - offset >= 4) { dataLength = BitConverter.ToInt32(buffer, offset); if (totalLength - offset - 4 >= dataLength) { // 接收到一个完整的数据 string data = Encoding.ASCII.GetString(buffer, offset + 4, dataLength); Console.WriteLine("Received: {0}", data); // 发送响应数据给客户端 byte[] response = Encoding.ASCII.GetBytes("Hello from server!"); stream.Write(response, 0, response.Length); offset += dataLength + 4; } else { // 数据完整,继续接收 break; } } // 移动数据到缓冲区的开头 totalLength -= offset; if (totalLength > 0) { Array.Copy(buffer, offset, buffer, 0, totalLength); } } } } // 关闭连接 client.Close(); Console.WriteLine("Client disconnected."); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } } ``` 该服务器使用一个循环来接收客户端发送的数据,它会不读取数据,直到接收到一个完整的数据。如果接收到的数据度大于 TcpListener 接收缓冲区的大小,它会将多个数据拼接成一个完整的数据。接收到完整的数据后,它会发送响应数据给客户端,并关闭连接。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿长大人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值