I'm revisiting a communications protocol parser design for a stream of bytes (serial data, received 1 byte at a time).
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) ||
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.
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.
Best Answer
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:
Edit: Some explanations of what is going on here:
First:
This line is a C# attribute (defined by
AcceptsAttribute
) says the theFooMessage
class accepts the message id of 5.Second:
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).
Third:
This line gets the following compiled lambda expression out of the dictionary and executes it:
(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 typeFunc<Message>
.)This lambda expression is built by the Value assignment line during dictionary creation:
(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:
But I believe that would be slower (and the cast here is necessary to change
Func<object>
intoFunc<Message>
).Fourth:
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).