Skip to content

The SerialMessage messaging convention

At the core of SyMqtt lies a lightweight, high-performance messaging convention implemented via the SerialMessage class. While standard MQTT natively operates on a pure asynchronous Publish-Subscribe pattern, SyMqtt abstracts this behavior to provide clean, synchronous Request-Response operations.

To achieve this without tying the library to a specific MQTT version or requiring heavy serialization overhead, SyMqtt handles correlation directly inside the message payload using a raw, comma-delimited text format.

While the MQTT v5 specification introduces native features to support request-response patterns (such as Response Topic and Correlation Data user properties), relying solely on them introduces two major challenges:

  • It breaks compatibility with older MQTT v3.1.1/v3.1.0 brokers;
  • It still requires developers to manually build the underlying plumbing—such as managing thread synchronization, handling timeouts, and mapping correlation IDs in memory.

SyMqtt takes a different, protocol-agnostic approach.
It implements a general-purpose virtualization layer that delivers seamless, synchronous Request-Response execution across all MQTT protocol versions (v3, v3.1.1, and v5). By managing execution tokens, TaskCompletionSource lifecycles, and memory cleanup automatically under the hood, SyMqtt provides a true plug-and-play solution.
You get a reliable RPC-like layer without writing any boilerplate tracking logic or worrying about the underlying broker’s capabilities.


A key architectural benefit of SyMqtt is unification: the exact same SerialMessage class is used to model both a Request and a Response. The underlying raw network packet remains a flat, comma-delimited string, but the semantic meaning of its fields adapts contextually based on the direction of the communication.

When a .NET application initiates a request, the fields serialized into the network string map directly to a correlation identifier and the raw execution directive:

[MessageId],[MessageContents]

  • MessageId: A unique alphanumeric string (typically a 32-character alphanumeric string generated via Guid.NewGuid().ToString("N")) used exclusively to correlate the incoming response back to the waiting execution thread.
  • MessageContents: The actual business data, command string, or structured text meant for the receiver.

Example of raw request string:

4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,START_PUMP_3

There are several ways to instantiate such a message. The most convenient is:

SerialMessage message = SerialMessage.NewMessage("START_PUMP_3");

This way, the new message’s ID is automatically initialized with a unique value (GUID).

When the receiving endpoint (e.g., another .NET service or an embedded device/PLC) answers a request, it must return the original MessageId followed by a structured status layout:

[MessageId],[SuccessCode],[Details]

  • MessageId: The exact ID copied from the request message.
  • SuccessCode: An integer indicating the execution status (typically 0 for success, or any non-zero value representing an error). The exact meaning and mapping of these error codes are entirely up to the developer consuming the library.
  • Details: The actual response content, error message, or return payload data.

Example of raw response string (Success):

4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,0,PUMP_3_RUNNING_1200_RPM

Example of raw response string (error/failure):

4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,104,ERROR_PRESSURE_TOO_HIGH

To unpack such a raw string into a SerialMessage instance (example):

string rawMessage = "4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,0,PUMP_3_RUNNING_1200_RPM";
SerialMessage message = SerialMessage.Parse(rawMessage);
// The extracted properties are now ready to be consumed:
Console.WriteLine($"Message ID: {message.MessageId}");
Console.WriteLine($"Success Code: {message.MessageSuccessCode}");
Console.WriteLine($"Details: {message.MessageDetails}");

When SerialMessage parses a string (incoming topic payload for example), it triggers an automatic multi-stage extraction process:

  1. Envelope splitting: The very first comma , encountered separates the MessageId from the remaining payload string (MessageContents).
  2. Sub-payload extraction: The class immediately runs an internal unpacking routine on the remaining MessageContents. It seeks a secondary comma to extract the MessageSuccessCode and the remaining string MessageDetails.
    If no second comma is found, the system gracefully treats the message as a raw payload, setting default codes safely: MessageSuccessCode is set to -1 (meaning unknown success code) and MessageDetails is set to "".

By encapsulating the unique message ID directly into the payload, SyMqtt ensures native compatibility across all MQTT versions (v3, v3.1.1, and v5).
This allows you to run high-level, synchronous message patterns seamlessly across legacy industrial infrastructure and modern cloud brokers alike.

2. High-performance, zero-allocation parsing

Section titled “2. High-performance, zero-allocation parsing”

Parsing structured payloads like JSON or XML requires heavy processing power and frequent memory allocations—luxuries that resource-constrained hardware or high-throughput backend gateways cannot waste.

To solve this, the internal parser is highly optimized to process incoming response strings with minimal resource consumption. By avoiding heap allocations during payload splitting, SyMqtt keeps the .NET Garbage Collector (GC) completely unaffected, maintaining maximum speed and deterministic latencies even under heavy, real-time communication stress.