Message framing, a length prefixed packet echo server

Most TCP servers deal with distinct messages whereas TCP itself deals in terms of a stream bytes. By default a single read from a TCP stream can return any number of bytes from 1 to the size of the buffer that you supplied. TCP knows nothing about your message structure. This is where message framing comes in. If the protocol that you are supporting has a concept of what constitutes a “message” then your protocol requires message framing. The simplest message framing is a length prefixed message where all messages start with a series of one, or more, bytes that convey the length of the message to the receiver. Another common message framing style is to have a terminating character, or characters, perhaps each message is terminated with a CR LF combination. Whatever your framing requirements WASP’s message framing handlers can help.

As we’ve seen with the previous tutorials, message framing in WASP is optional. If you don’t configure message framing for an end point then the message handler will receive a stream of bytes. It’s easier to write a message handler if you separate the message framing, if you do this then WASP will only ever pass complete messages to your message handler and your message framing code can be placed in a different DLL for easy reuse if required or it can reside in the same DLL as your message handling code.

A message framing DLL must export at least one function, GetMessageSize(). This function is passed the data that has arrived and its job is to look at that data, discover the message framing and work out how big the message is. As soon as GetMessageSize() knows the size of the message it’s dealing with it returns that value, if it can’t work it out yet then it returns 0 and it will be called when the next read completes.

This style of framing requires that all messages will fit in a single buffer. This is often easy to arrange but if it isn’t possible in your server design then you’ll have to do your own message framing and partial read buffering inside your message handler.

Message framing DLLs can also export another function, GetMinimumMessageSize(). This function is called once when the end point is started up and can return the minimum number of bytes that GetMessageSize() requires to calculate a message size. This is useful if, for example, you have a 4 byte message size at the start of each message, GetMinimumMessageSize() would return 4 and GetMessageSize() would never be called until WASP had accumulated at least 4 bytes of a new message.

The code for the simplest message framing DLL might look something like this:

unsigned long __cdecl GetMinimumMessageSize()
{
   return sizeof(int);
}

unsigned long __cdecl GetMessageSize(
   const WaspConnectionHandle /*connectionHandle*/,
   WaspConnectionUserData /*connectionUserData*/,
   const BYTE * const pData,
   const WaspDataLength /*dataLength*/,
   const WaspDataLength /*previousLength*/)
{
   return ntohl(*reinterpret_cast<const int *>(pData)) + sizeof(int);
}

Here we have message framing code for messages which start with an int in network byte order where the int contains the size of the message that follows. So, for example, if we were sending a message that consisted of the string Hello World we would first send an int, in network byte order, with a value of 11 and then send the 11 bytes that make up the message. Since our messages must be at least sizeof(int) bytes long before we can work out how big they are we provide an implementation of GetMinimumMessageSize() which returns that value.`

Our GetMessageSize() function will only ever be called with sizeof(int) bytes or more and so we can cast the input data to an int, convert it to host byte order and then add the size of the message header so that that the returned value includes the message length indicator and the message itself. An alternative would be for the message length indicator to already include the number of bytes that are required for it, but your protocol will dictate how this kind of thing works.

Once again we’ll ignore the connectionHandle and connectionUserData for now and since the message size is located at the front of the message and we have a GetMinimumMessageSize() function we can also ignore dataLength and previousLength. For more complex message framing previousLength can be used to optimise the scanning of the data buffer, we’ll show how that works in a later tutorial.

Once we have our message framing functions our message handler can be quite simple, in fact for this example our message handler is the same as for our simple Echo Server example.

WaspDataLength __cdecl  OnReadCompletedEx(
   const WaspConnectionHandle /*connectionHandle*/,
   WaspConnectionUserData /*connectionUserData*/,
   const BYTE * const /*pDataIn*/,
   const WaspDataLength dataLength,
   const BYTE * /*pDataOut*/,
   const WaspDataLength /*outputBufferSize*/)
{
   return dataLength;
}

The difference being that OnReadCompletedEx() will only be called once for each complete message rather than each time data arrives at the server.

This kind of server is harder to test from telnet as you need to send the correct message length byte sequence before your message. I’ll show you how you can use our EchoServerTest application to test both this and the simple echo server plugin in the next tutorial.

Once you have a framing DLL you need to configure WASP to use it. You do this by adding a FramingDLL value to the endpoint node in the config file.

<?xml version="1.0" encoding="Windows-1252"?>
<Configuration>
  <WASP>
    <TCP>
      <Endpoints>
        <EndPoint
          Name="Echo Server"
          Port="5050"
          FramingDLL="[CONFIG]\PacketEchoServerFraming.dll">
          HandlerDLL="[CONFIG]\EchoServer.dll">
        </EndPoint>
      </Endpoints>
    </TCP>
  </WASP>
</Configuration>

Your framing DLL entry points can be included in your message processing DLL if you prefer, or you can package them separately if you have multiple message processing DLLs which all use the same message framing.

You can download a simple example plugin which demonstrates this from here.