Handling packet structures in .NET - a new way?
As you may know, parsing and serializing network packets in .NET in a safe, performant and elegant way can be quite tricky. There are several ways of doing it, but none really satisfied me in all of these aspects.
Recently, I stumbled across ref structs which got introduced with C# 7.2. As you might know, OpenMU already handles incoming data packets as Span<byte>, because it never allocates data on the heap for each packet and therefore reduces stress at the garbage collector. It's basically a reference to a piece of memory, e.g. a part of a buffer array which might be part of an array pool. A Span<byte> is a ref struct as well. I don't want to go into details here, but these ref structs have some constraints, such as you can't hold them in a field of a class or a regular struct. However, you can hold it in a field of another ref struct - that's what I'm trying to do.
This allows to define ref structs like this:
public ref struct SomeMessage
{
private Span<byte> data;
private SomeMessage(Span<byte> data)
{
// You could add length/header type checks here :)
this.data = data;
}
public byte SomeField
{
get => this.data[2];
set => this.data[2] = value;
}
public static implicit operator SomeMessage(Span<byte> packet)
{
return new SomeMessage(packet);
}
}
I think you know what I have in mind, don't you? ;-) Basically, this message struct encapsulates the underlying Span<byte> and offers a simpler access to fields. Additionally, you can now implicitly cast a Span<byte> into a message structure like this:
void HandlePacket(Span<byte> data)
{
SomeMessage message = data;
Console.WriteLine(message.SomeField);
}
So, the packet handling code doesn't have to mess around with indexes anymore and isn't allocating memory on the heap. Of course, now the struct has to know all the field indexes, how these fields are aligned and how bytes are ordered. To solve this, I have another idea which I'll describe in another blog post.