串行数据的二进制通信协议解析器设计
我正在重新审视字节流的通信协议解析器设计(串行数据,一次接收1个字节)。
数据包结构(不能更改)是:
|| Start Delimiter (1 byte) | Message ID (1 byte) | Length (1 byte) | Payload (n bytes) | Checksum (1 byte) ||
过去,我已经采用程序状态机方法实现了这样的系统。 当每个数据字节到达时,状态机被驱动以查看输入数据一次/一个字节是否适合有效数据包,并且一旦整个数据包被组装,基于消息ID的switch语句执行适当的消息处理程序。 在一些实现中,解析器/状态机/消息处理程序循环位于其自己的线程中,以便不对串行数据接收的事件处理程序造成负担,并且由指示字节已被读取的信号量触发。
我想知道是否有更优雅的解决方案来解决这个常见问题,利用C#和OO设计的一些更现代的语言function。 任何可以解决这个问题的设计模式? 事件驱动vs polled vs组合?
我很想听听你的想法。 谢谢。
Prembo。
首先,我将数据包解析器与数据流读取器分开(这样我就可以在不处理流的情况下编写测试)。 然后考虑一个基类,它提供读入数据包的方法和写入数据包的方法。
另外,我会构建一个字典(一次只能再用于将来的调用),如下所示:
class Program { static void Main(string[] args) { var assembly = Assembly.GetExecutingAssembly(); IDictionary> messages = assembly .GetTypes() .Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract) .Select(t => new { Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true) .Cast().Select(attr => attr.MessageId), Value = (Func)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(); } }
编辑:对此处发生的事情的一些解释:
第一:
[Accepts(5)]
此行是C#属性(由AcceptsAttribute
定义),表示FooMessage
类接受消息ID为5。
第二:
是的,字典是在运行时通过reflection构建的。 你只需要这样做一次(我会把它放到一个单例类中,你可以在其上放置一个可以运行的测试用例,以确保字典正确构建)。
第三:
var m = messages[5]();
这一行从字典中获取以下编译的lambda表达式并执行它:
()=>(Message)new FooMessage();
(由于delagates工作方式的协变变化,在.NET 3.5中必须使用强制转换,但在4.0中, Func
类型的对象可以分配给Func
类型的对象。)
这个lambda表达式是在字典创建过程中由Value赋值行构建的:
Value = (Func)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(此处的强制转换是将已编译的lambda表达式Func
为Func
必要条件。)
我是这样做的,因为我恰好已经拥有了那种可用的类型。 你也可以使用:
Value = ()=>(Message)Activator.CreateInstance(t)
但我相信这会更慢(并且这里需要将Func
更改为Func
)。
第四:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
这样做是因为我觉得你可能有必要在一个类上多次放置AcceptsAttribute
(每个类接受多个消息id)。 这也有忽略没有消息id属性的消息类的好的副作用(否则Where方法需要具有确定属性是否存在的复杂性)。
我有点迟到了,但我写了一个框架,我认为可以做到这一点。 在不了解您的协议的情况下,我很难编写对象模型,但我认为这不会太难。 看看binaryserializer.com 。
我通常做的是定义一个抽象的基本消息类,并从该类派生密封的消息。 然后有一个消息解析器对象,它包含状态机来解释字节并构建适当的消息对象。 消息解析器对象只有一个方法(传递传入的字节)和可选的事件(在完整消息到达时调用)。
然后,您有两个处理实际消息的选项:
这两个选项在不同的场景中都很有用。
上述就是C#学习教程:串行数据的二进制通信协议解析器设计分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1035107.html