二进制通信协议分析程序设计的串行数据 [英] Binary communications protocol parser design for serial data
问题描述
我重新审视的通信协议分析程序设计的字节流(串行数据,以收到时间1个字节)。
I'm revisiting a communications protocol parser design for a stream of bytes (serial data, received 1 byte at a time).
数据包结构(可以' T为改变)是:
The packet structure (can't be changed) is:
|| Start Delimiter (1 byte) | Message ID (1 byte) | Length (1 byte) | Payload (n bytes) | Checksum (1 byte) ||
在过去,我已经在程序状态机的方法来实现这样的系统。数据的每一字节到达时,状态机被驱动以看到/如果输入数据在时间配合到一个有效的数据包的一个字节,并且一旦整个分组已组装的基础上,消息ID switch语句执行适当的处理程序的消息。在一些实施方式中,解析器/状态机/消息处理循环坐在其自己的线程,以便不负担接收到的事件处理程序中的串行数据,并且通过指示字节已被读出一个信号触发。
In the past I have implemented such systems in a procedural state-machine approach. As each byte of data arrives, the state machine is driven to see where/if the incoming data fits into a valid packet a byte at a time, and once a whole packet has been assembled, a switch statement based on the Message ID executes the appropriate handler for the message. In some implementations, the parser/state machine/message handler loop sits in its own thread so as not to burden the serial data received event handler, and is triggered by a semaphore indicating bytes have been read.
我想知道是否有一个更优雅的解决这个普遍存在的问题,利用一些C#和面向对象的设计更现代的语言特性。任何设计模式,将解决这个问题?事件驱动VS调查VS组合?
I'm wondering if there is a more elegant solution to this common problem, exploiting some of the more modern language features of C# and OO design. Any design patterns that would solve this problem? Event-driven vs polled vs combination?
我很感兴趣地听到你的想法。谢谢你。
I'm interested to hear your ideas. Thanks.
Prembo。
推荐答案
的一切我都分开从数据流中读取数据包分析器(这样我就可以写测试无需处理的第一流)。然后考虑一个基类,它提供了一个数据包,以读取和一种写数据包的方法。
First of all I would separate the packet parser from the data stream reader (so that I could write tests without dealing with the stream). Then consider a base class which provides a method to read in a packet and one to write a packet.
此外我将建立一个字典(仅一次然后再用它未来的呼叫)这样的:
Additionally I would build a dictionary (one time only then reuse it for future calls) like the following:
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(); }
}
编辑:几点说明什么是怎么回事的:
Some explanations of what is going on here:
首先:
[Accepts(5)]
这行是C#属性(定义为 AcceptsAttribute
)表示,在 FooMessage
类接受5消息ID
This line is a C# attribute (defined by AcceptsAttribute
) says the the FooMessage
class accepts the message id of 5.
二:
是字典正在运行时通过反射建成。你只需做一次(我把它变成一个单例类,你可以把一个测试用例它可以运行,以确保字典正确版本)。
Yes the dictionary is being built at runtime via reflection. You need only to do this once (I would put it into a singleton class that you can put a test case on it that can be run to ensure that the dictionary builds correctly).
第三:
var m = messages[5]();
这行得到以下编译lambda表达式了字典,并执行它:
This line gets the following compiled lambda expression out of the dictionary and executes it:
()=>(Message)new FooMessage();
(演员是必要的,.NET 3.5,但不是在4.0,由于在如何delagates协变变化工作中,在4.0类型的对象 Func键< FooMessage>
可分配给类型的对象 Func键<消息>
)
(The cast is necessary in .NET 3.5 but not in 4.0 due to the covariant changes in how delagates work, in 4.0 an object of type Func<FooMessage>
can be assigned to an object of the type Func<Message>
.)
这lambda表达式是由词典创建过程中的价值分配行建:
This lambda expression is built by the Value assignment line during dictionary creation:
Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile()
(这里的投就要投编译lambda表达式 Func键<消息方式>
)
(The cast here is necessary to cast the compiled lambda expression to Func<Message>
.)
我做了这种方式,因为我碰巧已经在这一点上提供给我的类型。你也可以使用:
I did that this way because I happen to already have the type available to me at that point. You could also use:
Value = ()=>(Message)Activator.CreateInstance(t)
但我相信这将是较慢的(这里的转换是需要改变 Func键<对象>
到 Func键<消息>
)。
But I believe that would be slower (and the cast here is necessary to change Func<object>
into Func<Message>
).
第四:
.SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value }))
这做,因为我觉得你可能在放置 AcceptsAttribute
不止一次在一个类值(接受每班多个消息ID)。这也有忽略那些没有消息id属性消息类的漂亮侧面影响(否则Where方法就需要有确定是否属性存在的复杂性)。
This was done because I felt that you might have value in placing the AcceptsAttribute
more than once on a class(to accept more than one message id per class). This also has the nice side affect of ignoring message classes that do not have a message id attribute (otherwise the Where method would need to have the complexity of determining if the attribute is present).
这篇关于二进制通信协议分析程序设计的串行数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!