Tutorial 9
Dealing with multiple uni-directional messages.
Some protocols define uni-directional messages that always travel one direction and never back, i.e. only sent or only received, but never both.
The CommsDSL allows specifying the direction of the message using sender property. The available values are "client", "server", and "both" (default). All the previous tutorials didn't use this property and as the result were assumed to be bi-directional.
The schema of this tutorial defines the following messages:
<message name="Msg1" id="MsgId.M1" displayName="^Msg1Name" sender="client" /> <message name="Msg2" id="MsgId.M2" displayName="^Msg2Name" sender="server" /> <message name="Msg3" id="MsgId.M3" displayName="^Msg3Name" sender="client" /> <message name="Msg4" id="MsgId.M4" displayName="^Msg4Name" sender="server" />
In this particular tutorial the client sends Msg1 and Msg3 to the server,
which responds with Msg2 and Msg4 respectively.
All the previous tutorials defined a single interface class for all the messages that implemented
various polymorphic behaviors including read and write. If such approach is
taken for this particular case, then all the messages will have various virtual
functions that are never really used. For example client application will never
invoke polymorphic read() of Msg1 and Msg3, while server will never invoke
polymorphic write() of these messages.
For cases like this, where majority of messages are uni-directional, it is highly recommended to split the messages into input and output ones and have different polymorphic interfaces for them defined. It will improve the generated code size as well as compilation speed.
Let's take a look at inner definitions inside the ServerSession. The common message interface for input messages is defined to be:
using InMessage = tutorial9::Message< comms::option::app::ReadIterator<const std::uint8_t*>, // Polymorphic read comms::option::app::Handler<ServerSession> // Polymorphic message dispatch >;
It requires only polymorphic read and polymorphic dispatch. The input messages are defined as:
using InMsg1 = tutorial9::message::Msg1<InMessage>; using InMsg3 = tutorial9::message::Msg3<InMessage>;
While the common interface for output messages is defined to be:
using OutMessage = tutorial9::Message< comms::option::app::WriteIterator<std::uint8_t*>, // Polymorphic write comms::option::app::LengthInfoInterface, // Polymorphic length calculation comms::option::app::IdInfoInterface, // Polymorphic message ID retrieval comms::option::app::NameInterface // Polymorphic message name retrieval >;
While the output messages themselves are:
using OutMsg2 = tutorial9::message::Msg2<OutMessage>; using OutMsg4 = tutorial9::message::Msg4<OutMessage>;
There is also an important thing to note. The commsdsl2comms code generator produces include/tutorial9/input/ClientInputMessages.h and include/tutorial9/input/ServerInputMessages.h which define input messages for the client and server sides respectively.
Please also pay closer attention to the frame class definition inside include/tutorial9/frame/Frame.h.
template < typename TMessage, typename TAllMessages = tutorial9::input::AllMessages<TMessage>, typename TOpt = tutorial9::options::DefaultOptions > class Frame ...
All the previous tutorials provided only the common message interface class as the first template parameter while leaving all others as default. The second template parameter specifies all input messages the frame is expected to recognize in order to properly create message object. The default configuration is to use all the defined messages (defined in include/tutorial9/input/AllMessages.h).
In this tutorial the server needs to recognize only limited number of messages, hence it can use tutorial9::input::ServerInputMessages instead of default tutorial9::input::AllMessages
using Frame =
tutorial9::frame::Frame<
InMessage,
tutorial9::input::ServerInputMessages<InMessage>
>;In such case if any of the unexpected messages (like Msg2 or Msg4) arrives from the
client, they will just be ignored because the frame won't recognize them and appropriate
message object won't even be created to perform a dispatch for handling.
The ClientSession looks very similar:
class ClientSession : public Session { public: ... using InMessage = tutorial9::Message< comms::option::app::ReadIterator<const std::uint8_t*>, // Polymorphic read comms::option::app::Handler<ClientSession> // Polymorphic message dispatch >; using InMsg2 = tutorial9::message::Msg2<InMessage>; using InMsg4 = tutorial9::message::Msg4<InMessage>; void handle(InMsg2& msg); void handle(InMsg4& msg); void handle(InMessage& msg); private: using OutMessage = tutorial9::Message< comms::option::app::WriteIterator<std::back_insert_iterator<std::vector<std::uint8_t> > >, // Polymorphic write comms::option::app::LengthInfoInterface, // Polymorphic length calculation comms::option::app::IdInfoInterface, // Polymorphic message ID retrieval comms::option::app::NameInterface // Polymorphic message name retrieval >; using OutMsg1 = tutorial9::message::Msg1<OutMessage>; using OutMsg3 = tutorial9::message::Msg3<OutMessage>; using Frame = tutorial9::frame::Frame< InMessage, tutorial9::input::ClientInputMessages<InMessage> >; ... };
Please pay attention that the frame uses tutorial9::input::ClientInputMessages as the list of input messages suitable for the client.
Now it's time to take a closer look at the client code that sends the messages out to the server.
void ClientSession::sendMsg1() { // This code sends legitimate message expected by the server OutMsg1 msg; sendMessage(msg); // This code demonstrates sending unexpected message ignored by the server using OutMsg2 = tutorial9::message::Msg2<OutMessage>; OutMsg2 messageToIgnore; sendMessage(messageToIgnore); } void ClientSession::sendMsg3() { // This code demonstrates sending unexpected message ignored by the server using OutMsg4 = tutorial9::message::Msg4<OutMessage>; OutMsg4 messageToIgnore; sendMessage(messageToIgnore); // This code sends legitimate message expected by the server OutMsg3 msg; sendMessage(msg); }
Although Msg2 and Msg4 are marked as sent only by the server and
never by the client, nothing prevents us from creating such output
messages by the client and successfully send them to the server.
The latter however does NOT include Msg2 and Msg4 as its input
messages and will just ignore them.
Another thing to note is that the Frame definition requires only
knowledge about the input messages that it can create and process during read()
operation. The write()
functionality of the frame supports any type of message interface class and
does NOT care about a list of supported output messages.
Summary
- The CommsDSL allows definition of the direction of the message using sender property.
- When majority of the protocol messages are uni-directional it is highly recommended to split the common interface class into two for input and output messages to avoid generation of unnecessary virtual functions.
- The frame definition requires information about input message interface as well as list of supported input messages and does NOT care how the output messages are defined.
- The generated code contains include/<namespace>/input/ClientInputMessages.h and include/<namespace>/input/ServerInputMessages.h to define list of input messages relevant for client and server respectively.
- The default list of input messages for the frame is include/<namespace>/input/AllMessages.h. It is highly recommended to replace it with more appropriate one depending on the end application logic.
Read Previous Tutorial <-----------------------> Read Next Tutorial