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];
|
}
|
}
|