wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace CanSimulator
{
    /// <summary>
    /// CAN 模拟器 TCP 线协议分帧编解码器。
    /// 线格式:4 字节大端长度前缀 + UTF8 文本行(与 BrokerIpc 同惯用法)。
    /// 读侧负责粘包/半包重组:循环读满长度前缀与帧体后返回完整文本。
    /// </summary>
    public static class SimulatorFraming
    {
        /// <summary>长度前缀字节数</summary>
        public const int HeaderSize = 4;
 
        /// <summary>单帧最大字节数(防畸形长度前缀导致内存爆掉;命令串与 LOG 响应远小于此值)</summary>
        public const int MaxFrameBytes = 1024 * 1024;
 
        /// <summary>把一条文本消息编码为完整帧(含长度前缀)</summary>
        public static byte[] Encode(string text)
        {
            if (text == null) throw new ArgumentNullException(nameof(text));
            byte[] body = Encoding.UTF8.GetBytes(text);
            if (body.Length > MaxFrameBytes)
                throw new InvalidOperationException($"帧体超限: {body.Length} > {MaxFrameBytes}");
            var frame = new byte[HeaderSize + body.Length];
            WriteBigEndian(frame, 0, body.Length);
            Buffer.BlockCopy(body, 0, frame, HeaderSize, body.Length);
            return frame;
        }
 
        /// <summary>解码已去掉长度前缀的帧体</summary>
        public static string Decode(byte[] buffer, int offset, int count)
            => Encoding.UTF8.GetString(buffer, offset, count);
 
        /// <summary>
        /// 从流中读取一个完整帧。
        /// 返回 null 表示对端已关闭或帧中途 EOF(连接失效,调用方应触发重连);
        /// 长度前缀非法(≤0 或超 MaxFrameBytes)抛 InvalidDataException(协议失步)。
        /// </summary>
        public static async Task<string?> ReadFrameAsync(Stream s, CancellationToken ct)
        {
            var header = new byte[HeaderSize];
            if (!await ReadExactAsync(s, header, HeaderSize, ct).ConfigureAwait(false))
                return null;
            int len = ReadBigEndian(header, 0);
            if (len <= 0 || len > MaxFrameBytes)
                throw new InvalidDataException($"非法帧长度前缀: {len}");
 
            var body = new byte[len];
            if (!await ReadExactAsync(s, body, len, ct).ConfigureAwait(false))
                return null; // 帧体中途 EOF
            return Decode(body, 0, len);
        }
 
        /// <summary>循环读满 count 字节;期间 EOF(读到 0 字节)或 IO 异常时返回 false,由调用方按连接失效处理</summary>
        private static async Task<bool> ReadExactAsync(Stream s, byte[] buf, int count, CancellationToken ct)
        {
            int off = 0;
            while (off < count)
            {
                int n;
                try { n = await s.ReadAsync(buf, off, count - off, ct).ConfigureAwait(false); }
                catch (IOException) { return false; }
                if (n <= 0) return false;
                off += n;
            }
            return true;
        }
 
        // net472 无 System.Buffers.Binary,手写大端读写
        private static void WriteBigEndian(byte[] buf, int offset, int value)
        {
            buf[offset] = (byte)(value >> 24);
            buf[offset + 1] = (byte)(value >> 16);
            buf[offset + 2] = (byte)(value >> 8);
            buf[offset + 3] = (byte)value;
        }
 
        private static int ReadBigEndian(byte[] buf, int offset)
            => (buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3];
    }
}