C++ network protocol classes

I'm sure everyone here has seen network protocol code that was just about raw stream IO on the client and the server side at different code locations. A very simple approach to get around of this is using a simple C++ class for each message you send, provide a read and write method and you are done. I know this sounds easy - but then again - why has everybody seen the above mentioned style?

Before anyone points this out: Yes, I know protobuf and other things that can do this a lot lot better.

Anyway, let's continue with a very simple approach to do this kind of things. I assume that there is a class to handle the network stream (ByteStream in the example code):

Interface

typedef uint8_t protocolId;

class IProtocolMessage {
protected:
 protocolId _id;

public:
 IProtocolMessage (protocolId id) :
   _id(id)
 {
 }

 virtual ~IProtocolMessage ()
 {
 }

 inline protocolId getId () const
 {
  return _id;
 }

 virtual void serialize (ByteStream& out) const = 0;
}; 

Usage

We can now use this interface to e.g. create a message like this:

#pragma once

#include "IProtocolMessage.h"
#include <string>

class SomeFancyMessage: public IProtocolMessage {
private:
 uint16_t _entityId;
 bool _state;
public:
 SomeFancyMessage (uint16_t entityId, bool state) :
   IProtocolMessage(protocol::PROTO_SOMEFANCYMESSAGE), _entityId(entityId), _state(state)
 {
 }

 SomeFancyMessage (ByteStream& input) :
   IProtocolMessage(protocol::PROTO_SOMEFANCYMESSAGE)
 {
  _entityId = input.readShort();
  _state = input.readBool();
 }

 void serialize (ByteStream& out) const override
 {
  out.addByte(_id);
  out.addShort(_entityId);
  out.addBool(_state);
 }

 inline uint16_t getEntityId () const
 {
  return _entityId;
 }

 inline bool getState () const
 {
  return _state;
 }
}; 

What's the point?

Now that we have these classes, what the heck is the point in doing it like this and not inline wherever it is needed? (I really hope that nobody who reads this would really ask this question)
  • A single point where your protocol is defined, not copied in server and client code.
  • Typesafe parameters for reading and writing values. As a caller you don't care about the serialization of your given contructor parameters. (See my Enum<T> class as an example)
  • This stuff can get autogenerated and factories can get created around it automatically, too.

IProtocolMessage *ProtocolMessageFactory::create (ByteStream& stream)
{
 const protocolId type = stream.readByte();

 switch (type) {
 case protocol::PROTO_SOMEFANCYMESSAGE:
  return new SomeFancyMessage(stream);
 [..]
 }
}

Notes

This example and very simplified code would only support 256 different protocol messages due to the uint8_t data type for the protocol id.

Kommentare

Beliebte Posts