-
Notifications
You must be signed in to change notification settings - Fork 2
Tutorials
To create a protocol you have inherit from another protocol via the [Curiously recurring template pattern] (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). This basically means to inherit from class that has your class as a template argument. Then you have to implement certain callback methods that will be called once data arrives on your socket.
If we have a look at the following example everything should become clear:
#include <twisted/reactor.hpp>
#include <twisted/basic_protocol.hpp>
#include <twisted/default_factory.hpp>
// [1] CRTP happening here
struct echo_protocol : twisted::basic_protocol<echo_protocol> {
// [2] this message will be called once a certain amount of data received
void on_message(const_buffer_iterator begin, const_buffer_iterator end) {
// [3] use the send_message function to reply to your peer
send_message(begin, end);
}
};
int main() {
// [4] create a reactor/event loop
twisted::reactor reac;
// [5] listen on tcp port 50000 with your echo protocol
reac.listen_tcp(50000, twisted::default_factory<echo_protocol>());
// we could add more protocols here
// [6] run all your protocols
reac.run();
}We see a lot of stuff going on here. [1] shows the basic struct/class definition on how to defined a protocol. We inherit from the most basic protocol - the basic_protocol. The basic_protocol will call the on_message(const_buffer_iterator, const_buffer_iterator) function whenever data is available on your connection. The the two iterators build the range that contains the data that was received [2]. In [3] we see how to reply data - by using the send_message(iterator, iterator) function. The two iterators here build the range that will be sent. In this example we simply send the data we received back to the sender - a simple echo protocol.
To get our event loop running we need a reactor [4]. In [5] we tell our reactor on which ports we want to listen and which protocol should be used for each port. We also see that we pass a factory. Factories will be used to create instances of your protocol. In the next chapter factories will be explained more in depth. Finally we tell our event loop to run and accept connections [6].
If we take look at the declaration of the reactor::listen_tcp function:
template <typename ProtocolFactory>
void listen_tcp(int port, ProtocolFactory factory);We see that it takes a ProtocolFactory as shown in the first example. A ProtocolFactory is a simple function object that once called creates an instance of the protocol you want to use for the given port.
The twisted::default_factory<ProtocolType>() convenience function will create a factory that default constructs your protocol. So basically we could rewrite the first example to:
reac.listen_tcp(50000, [] () { return echo_protocol(); });Custom factories, or a simple lambda, can be useful if you want to pass arguments to your protocol.
Currently there are two kinds of transport types - TCP and SSL. The respective functions are:
template <typename ProtocolFactory>
void listen_tcp(int port, ProtocolFactory factory);
template <typename ProtocolFactory>
void listen_ssl(int port, ProtocolFactory factory, ssl_options&& ssl_opts);You already know the first one. The ssl version takes an extra argument that contains the ssl_options. ssl_options is a typedef for the boost::asio::ssl_context. So you can configure everything that is supported there. Note that we explicitly take a rvalue-reference here as we have to take ownership of the context.
The header ssl_options.hpp provides a convenience function default_ssl_options:
ssl_options default_ssl_options(std::string certificate_filename,
std::string key_filename,
std::string password);This function will create an options object with SSL3 enabled, the given certificate-chain file and the password for a PEM key file.
There are already some predefined protocols that you can use to implement your application logic.
This protocol provides on_message callback which is called whenever data is received.
The twisted::line_receiver implements string based delimiter parsing. The callback line_received will be called whenever a stream of data is terminted with a certain delimiter.
struct line_receiver_echo
: twisted::line_receiver<line_receiver_echo> {
// delimter is passed to as constructor argument to the linereciever
line_receiver_echo() : line_receiver("\r\n\r\n") {}
// Note: the range [begin, end) doesn't contain the delimiter
void line_received(const_buffer_iterator begin, const_buffer_iterator end) {
// will send the range [begin, end) and append the delimiter
send_line(begin, end);
}
}; Sending the sequence "AAA\r\n\r\nBBB\r\n\r\nCCC" to the above protocol would call line_received twice and reply with "AAA\r\n\r\n" and "BBB\r\n\r\n".
Note that there is also a default delimiter of "\r\n".