串行数据的二进制通信协议解析器设计

2023-11-24

我正在重新审视字节流(串行数据,一次接收 1 个字节)的通信协议解析器设计。

数据包结构(无法更改)为:

|| Start Delimiter (1 byte) | Message ID (1 byte) | Length (1 byte) | Payload (n bytes) | Checksum (1 byte) ||

过去,我以过程状态机方法实现了此类系统。当数据的每个字节到达时,状态机被驱动以查看传入数据在哪里/是否适合一次一个字节地进入有效数据包,并且一旦组装了整个数据包,基于消息 ID 的 switch 语句就会执行消息的适当处理程序。在一些实现中,解析器/状态机/消息处理程序循环位于其自己的线程中,以便不给串行数据接收事件处理程序带来负担,并且由指示字节已被读取的信号量触发。

我想知道是否有一个更优雅的解决方案来解决这个常见问题,利用 C# 和 OO 设计的一些更现代的语言功能。有什么设计模式可以解决这个问题吗?事件驱动、轮询、组合?

我有兴趣听听你的想法。谢谢。

Prembo.


首先,我将数据包解析器与数据流读取器分开(这样我就可以编写测试而不处理流)。然后考虑一个基类,它提供了一种读取数据包的方法和一种写入数据包的方法。

另外,我会构建一个字典(仅一次,然后将其用于将来的调用),如下所示:

class Program {
    static void Main(string[] args) {
        var assembly = Assembly.GetExecutingAssembly();
        IDictionary<byte, Func<Message>> messages = assembly
            .GetTypes()
            .Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract)
            .Select(t => new {
                Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true)
                       .Cast<AcceptsAttribute>().Select(attr => attr.MessageId),
                Value = (Func<Message>)Expression.Lambda(
                        Expression.Convert(Expression.New(t), typeof(Message)))
                        .Compile()
            })
            .SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
            .ToDictionary(o => o.Key, v => v.Value); 
            //will give you a runtime error when created if more 
            //than one class accepts the same message id, <= useful test case?
        var m = messages[5](); // consider a TryGetValue here instead
        m.Accept(new Packet());
        Console.ReadKey();
    }
}

[Accepts(5)]
public class FooMessage : Message {
    public override void Accept(Packet packet) {
        Console.WriteLine("here");
    }
}

//turned off for the moment by not accepting any message ids
public class BarMessage : Message {
    public override void Accept(Packet packet) {
        Console.WriteLine("here2");
    }
}

public class Packet {}

public class AcceptsAttribute : Attribute {
    public AcceptsAttribute(byte messageId) { MessageId = messageId; }

    public byte MessageId { get; private set; }
}

public abstract class Message {
    public abstract void Accept(Packet packet);
    public virtual Packet Create() { return new Packet(); }
}

编辑:对这里发生的事情的一些解释:

First:

[Accepts(5)]

这一行是一个 C# 属性(定义为AcceptsAttribute)说FooMessage类接受消息 ID 5。

Second:

是的,字典是在运行时通过反射构建的。您只需要执行一次(我会将其放入一个单例类中,您可以在其上放置一个测试用例,该测试用例可以运行以确保字典正确构建)。

Third:

var m = messages[5]();

此行从字典中获取以下已编译的 lambda 表达式并执行它:

()=>(Message)new FooMessage();

(强制转换在 .NET 3.5 中是必要的,但在 4.0 中则不需要,因为延迟工作方式发生了协变变化,在 4.0 中,类型的对象Func<FooMessage>可以分配给该类型的对象Func<Message>.)

该 lambda 表达式是在字典创建期间由 Value 赋值行构建的:

Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()

(此处的强制转换是将已编译的 lambda 表达式强制转换为Func<Message>.)

我这样做是因为当时我碰巧已经有了可用的类型。您还可以使用:

Value = ()=>(Message)Activator.CreateInstance(t)

但我相信会比较慢(而且这里的演员阵容有必要改变)Func<object> into Func<Message>).

Fourth:

.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))

这样做是因为我觉得您将AcceptsAttribute在一个班级上多次(每个班级接受多个消息 ID)。这还有一个很好的副作用,即忽略没有消息 id 属性的消息类(否则Where 方法需要具有确定该属性是否存在的复杂性)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

串行数据的二进制通信协议解析器设计 的相关文章

  • 如何验证文件名称在 Windows 中是否有效?

    是否有一个 Windows API 函数可以将字符串值传递给该函数 该函数将返回一个指示文件名是否有效的值 我需要验证文件名是否有效 并且我正在寻找一种简单的方法来完成此操作 而无需重新发明轮子 我正在直接使用 C 但针对的是 Win32
  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • pthread_cond_timedwait() 和 pthread_cond_broadcast() 解释

    因此 我在堆栈溢出和其他资源上进行了大量搜索 但我无法理解有关上述函数的一些内容 具体来说 1 当pthread cond timedwait 因为定时器值用完而返回时 它如何自动重新获取互斥锁 互斥锁可能被锁定在其他地方 例如 在生产者
  • 实时服务器上的 woff 字体 MIME 类型错误

    我有一个 asp net MVC 4 网站 我在其中使用 woff 字体 在 VS IIS 上运行时一切正常 然而 当我将 pate 上传到 1and1 托管 实时服务器 时 我得到以下信息 网络错误 404 未找到 http www co
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 如果使用 SingleOrDefault() 并在数字列表中搜索不在列表中的数字,如何返回 null?

    使用查询正数列表时SingleOrDefault 当在列表中找不到数字时 如何返回 null 或像 1 这样的自定义值 而不是类型的默认值 在本例中为 0 你可以使用 var first theIntegers Cast
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • Github Action 在运行可执行文件时卡住

    我正在尝试设置运行google tests on a C repository using Github Actions正在运行的Windows Latest 构建过程完成 但是当运行测试时 它被卡住并且不执行从生成的可执行文件Visual
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 如何在 VBA 中声明接受 XlfOper (LPXLOPER) 类型参数的函数?

    我在之前的回答里发现了问题 https stackoverflow com q 19325258 159684一种无需注册即可调用 C xll 中定义的函数的方法 我之前使用 XLW 提供的注册基础结构 并且使用 XlfOper 类型在 V
  • 在 Dynamics CRM 插件中访问电子邮件发件人地址

    我正在编写一个 Dynamics CRM 2011 插件 该插件挂钩到电子邮件实体的更新后事件 阶段 40 pipeline http msdn microsoft com en us library gg327941 aspx 并且在此阶
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • C - 直接从键盘缓冲区读取

    这是C语言中的一个问题 如何直接读取键盘缓冲区中的数据 我想直接访问数据并将其存储在变量中 变量应该是什么数据类型 我需要它用于我们研究所目前正在开发的操作系统 它被称为 ICS OS 我不太清楚具体细节 它在 x86 32 位机器上运行
  • ASP.NET MVC 6 (ASP.NET 5) 中的 Application_PreSendRequestHeaders 和 Application_BeginRequest

    如何在 ASP NET 5 MVC6 中使用这些方法 在 MVC5 中 我在 Global asax 中使用了它 现在呢 也许是入门班 protected void Application PreSendRequestHeaders obj
  • 限制C#中的并行线程数

    我正在编写一个 C 程序来生成并通过 FTP 上传 50 万个文件 我想并行处理4个文件 因为机器有4个核心 文件生成需要更长的时间 是否可以将以下 Powershell 示例转换为 C 或者是否有更好的框架 例如 C 中的 Actor 框
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我

随机推荐