Skip to content

Commit af35d63

Browse files
committed
tutorial26 text.
1 parent 8578eae commit af35d63

File tree

4 files changed

+154
-16
lines changed

4 files changed

+154
-16
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ $> nmake install
180180
- [tutorial23](../../tree/master/tutorials/tutorial23) - Reusing definitions from other schemas.
181181
- [tutorial24](../../tree/master/tutorials/tutorial24) - Extra validity verification of message payload.
182182
- [tutorial25](../../tree/master/tutorials/tutorial25) - Dealing with big protocols.
183+
- [tutorial26](../../tree/master/tutorials/tutorial26) - Stateful messages sequence.
183184

184185

185186
# How-Tos

tutorials/tutorial26/README.md

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,133 @@
11
# Tutorial 26
2-
Stateful message sequence.
2+
Stateful messages sequence.
3+
4+
There are protocols that don't differentiate messages by their IDs. Instead, the sequence of the messages is pre-determined.
5+
In this particular tutorial (see [schema](dsl/schema.xml)) all the messages are the same, i.e. have the same ID and without any fields.
6+
```
7+
<message name="Msg1" id="0" order="0" displayName="^Msg1Name" />
8+
<message name="Msg2" id="0" order="1" displayName="^Msg2Name" />
9+
<message name="Msg3" id="0" order="2" displayName="^Msg3Name" />
10+
```
11+
Please note usage of the `nonUniqueMsgIdAllowed="true"` in the schema definition as well as **order** property for each `<message>`
12+
13+
The frame is defined like this:
14+
```
15+
<frame name="Frame">
16+
<size name="Size">
17+
<int name="SizeField" type="uint16" />
18+
</size>
19+
<id name="Id">
20+
<int name="IdField" type="uint8" pseudo="true" />
21+
</id>
22+
<payload name="Data" />
23+
</frame>
24+
```
25+
Note that the field for the message ID is marked with `pseudo="true"`, i.e it is not serialized. Basically the real frame is
26+
```
27+
| SIZE (2 bytes) | PAYLOAD |
28+
```
29+
30+
In most of the previous tutorials the messages were decoded and dispatched using the [comms::processAllWithDispatch()](https://commschamp.github.io/comms_doc/process_8h.html)
31+
function, which internally invoked the `read()` member function of the outermost
32+
[framing layer](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgSizeLayer.html).
33+
The first parameter to the function can be a reference to a smart pointer (a variant of the `std::unique_ptr`) to the message object or a
34+
reference to the message object itself. When a smart pointer invocation is used the message object is dynamically allocated by the
35+
[comms::frame::MsgIdLayer](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgIdLayer.html) framing layer. However,
36+
when a reference to already allocated message object is used it cannot be changed and the processed message ID information must match
37+
the ID reported by the message object. The [comms::processAllWithDispatch()](https://commschamp.github.io/comms_doc/process_8h.html) function
38+
used in previous tutorial internally uses the reference to a smart pointer invocation variant. To be able to support stateful sequence of message
39+
objects there is a need to use a different function ([comms::processSingleWithDispatch](https://commschamp.github.io/comms_doc/process_8h.html))
40+
and process messages one by one while passing the reference to the pre-allocated message object.
41+
42+
Let's take a look at the [server](src/ServerSession.cpp) implementation.
43+
```cpp
44+
std::size_t ServerSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen)
45+
{
46+
std::cout << "Processing input: " << std::hex;
47+
std::copy_n(buf, bufLen, std::ostream_iterator<unsigned>(std::cout, " "));
48+
std::cout << std::dec << std::endl;
49+
50+
assert(m_msg);
51+
auto* prev = buf;
52+
auto consumed = 0U;
53+
while (consumed < bufLen) {
54+
auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this);
55+
if (es != comms::ErrorStatus::Success) {
56+
std::cerr << "ERROR: unexpected protocol sequence" << std::endl;
57+
return consumed;
58+
}
59+
60+
consumed += static_cast<std::size_t>(std::distance(prev, buf));
61+
}
62+
return consumed;
63+
}
64+
```
65+
The message object is pre-allocated before at the start:
66+
```cpp
67+
bool ServerSession::startImpl()
68+
{
69+
m_msg.reset(new Msg1);
70+
return true;
71+
}
72+
```
73+
It means that when the first data buffer is reported the `Msg1` object is pre-allocated and is properly decoded.
74+
75+
When it is properly handled, the next message object (`Msg2`) is pre-allocated for the next input and so on.
76+
```cpp
77+
void ServerSession::handle(Msg1& msg)
78+
{
79+
std::cout << "Received message \"" << msg.doName() << "\"" << std::endl;
80+
sendMessage(msg);
81+
m_msg.reset(new Msg2);
82+
}
83+
```
84+
85+
The [client](src/ClientSession.cpp) is very similar. The input message is pre-allocated every send.
86+
```cpp
87+
void ClientSession::sendMsg1()
88+
{
89+
m_msg.reset(new Msg1);
90+
sendMessage(*m_msg);
91+
}
92+
93+
void ClientSession::sendMsg2()
94+
{
95+
m_msg.reset(new Msg2);
96+
sendMessage(*m_msg);
97+
}
98+
99+
...
100+
```
101+
102+
When exchanging the messages both client and server allocate and dispatch proper message object (sequentially) for the same
103+
input. Below is the expected client side output.
104+
```
105+
Sending message "Message 1" with ID=0
106+
Sending raw data: 0 0
107+
Processing input: 0 0
108+
Received message "Message 1"
109+
Sending message "Message 2" with ID=0
110+
Sending raw data: 0 0
111+
Processing input: 0 0
112+
Received message "Message 2"
113+
Sending message "Message 3" with ID=0
114+
Sending raw data: 0 0
115+
Processing input: 0 0
116+
Received message "Message 3"
117+
```
118+
119+
Note that the `read()` member function of a [frame](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgIdLayer.html) can
120+
receive a reference to the interface class (extends [comms::Message](https://commschamp.github.io/comms_doc/classcomms_1_1Message.html)) or
121+
the actual message object (extends [comms::MessageBase](https://commschamp.github.io/comms_doc/classcomms_1_1MessageBase.html)). In case of
122+
the latter the code will attempt to invoke non-virtual member functions of the message object, such as `doRead()` instead of polymorphic
123+
`read()`.
124+
125+
## Summary
126+
127+
- The transport framing allows deserialization of the pre-allocated message object, which allows implementation of the
128+
stateful sequence of messages.
129+
- The processing functions from the [comms/process.h](https://commschamp.github.io/comms_doc/process_8h.html) wrap the
130+
frame function(s) invocations and allow transparent passing of the same message parameter to the frame function,
131+
whether it is a reference to the message object itself or a reference to a smart pointer to the message object.
3132

4133
[Read Previous Tutorial](../tutorial25) &lt;-----------------------

tutorials/tutorial26/src/ClientSession.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ std::size_t ClientSession::processInputImpl(const std::uint8_t* buf, std::size_t
2828

2929
assert(m_msg);
3030
auto* prev = buf;
31-
auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this);
32-
if (es != comms::ErrorStatus::Success) {
33-
std::cerr << "ERROR: unexpected protocol sequence" << std::endl;
34-
return bufLen;
35-
}
31+
auto consumed = 0U;
32+
while (consumed < bufLen) {
33+
auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this);
34+
if (es != comms::ErrorStatus::Success) {
35+
std::cerr << "ERROR: unexpected protocol sequence" << std::endl;
36+
return consumed;
37+
}
3638

37-
return static_cast<std::size_t>(std::distance(prev, buf));
39+
consumed += static_cast<std::size_t>(std::distance(prev, buf));
40+
}
41+
return consumed;
3842
}
3943

4044
void ClientSession::sendMessage(const Message& msg)
@@ -107,19 +111,19 @@ void ClientSession::doNextStage()
107111
void ClientSession::sendMsg1()
108112
{
109113
m_msg.reset(new Msg1);
110-
sendMessage(*m_msg); // Should get received and echoed back
114+
sendMessage(*m_msg);
111115
}
112116

113117
void ClientSession::sendMsg2()
114118
{
115119
m_msg.reset(new Msg2);
116-
sendMessage(*m_msg); // Should get received and echoed back
120+
sendMessage(*m_msg);
117121
}
118122

119123
void ClientSession::sendMsg3()
120124
{
121125
m_msg.reset(new Msg3);
122-
sendMessage(*m_msg); // Should get received and echoed back
126+
sendMessage(*m_msg);
123127
}
124128

125129
SessionPtr Session::createClient(boost_wrap::io& io)

tutorials/tutorial26/src/ServerSession.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ std::size_t ServerSession::processInputImpl(const std::uint8_t* buf, std::size_t
5050

5151
assert(m_msg);
5252
auto* prev = buf;
53-
auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this);
54-
if (es != comms::ErrorStatus::Success) {
55-
std::cerr << "ERROR: unexpected protocol sequence" << std::endl;
56-
return bufLen;
53+
auto consumed = 0U;
54+
while (consumed < bufLen) {
55+
auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this);
56+
if (es != comms::ErrorStatus::Success) {
57+
std::cerr << "ERROR: unexpected protocol sequence" << std::endl;
58+
return consumed;
59+
}
60+
61+
consumed += static_cast<std::size_t>(std::distance(prev, buf));
5762
}
58-
59-
return static_cast<std::size_t>(std::distance(prev, buf));
63+
return consumed;
6064
}
6165

6266
void ServerSession::sendMessage(const Message& msg)

0 commit comments

Comments
 (0)