blob: dcfbe057d1f2a0c9a6df840d2e4bb193d33c4b87 [file] [log] [blame]
// -*-markdown-*-
// NOTE: doxygen can include markdown pages directly but there seems to be a bug
// that shows messed-up line numbers in \skip \until code extracts so this file
// is markdown wrapped in a C++ comment - which works.
/**\page tutorial Tutorial
This is a brief tutorial that will walk you through the fundamentals of building
messaging applications in incremental steps. There are further examples, in
addition the ones mentioned in the tutorial.
Some of the examples require an AMQP *broker* that can receive, store and send
messages. \ref broker.hpp and \ref broker.cpp define a simple example
broker. Run without arguments it listens on `0.0.0.0:5672`, the standard AMQP
port on all network interfaces. To use a different port or network interface:
broker -a <host>:<port>
Instead of the example broker, you can use any AMQP 1.0 compliant broker. You
must configure your broker to have a queue (or topic) named "examples".
The `helloworld` examples take an optional URL argument. The other examples take
an option `-a URL`. A URL looks like:
HOST:PORT/ADDRESS
It usually defaults to `127.0.0.1:5672/examples`, but you can change this if
your broker is on a different host or port, or you want to use a different queue
or topic name (the ADDRESS part of the URL). URL details are at `proton::url`
The first part of the tutorial uses the `proton::container`, later we will
show some of the same examples implemented using the `proton::connection_engine`.
Most of the code is the same for either approach.
Hello World!
------------
\dontinclude helloworld.cpp
Tradition dictates that we start with hello world! This example sends a message
to a broker and the receives the same message back to demonstrate sending and
receiving. In a realistic system the sender and receiver would normally be in
different processes. The complete example is \ref helloworld.cpp
We will include the following classes: `proton::container` runs an event loop
which dispatches events to a `proton::handler`. This allows a *reactive*
style of programming which is well suited to messaging applications. `proton::url` is a simple parser for the URL format mentioned above.
\skip proton/container
\until proton/url
We will define a class `hello_world` which is a subclass of
`proton::handler` and over-rides functions to handle the events
of interest in sending and receiving a message.
\skip class hello_world
\until {}
`on_start()` is called when the event loop first starts. We handle that by
establishing a connection and creating a sender and a receiver.
\skip on_start
\until }
`on_sendable()` is called when message can be transferred over the associated
sender link to the remote peer. We create a `proton::message`, set the message
body to `"Hello World!"` and send the message. Then we close the sender as we only
want to send one message. Closing the sender will prevent further calls to
`on_sendable()`.
\skip on_sendable
\until }
`on_message()` is called when a message is received. We just print the body of
the message and close the connection, as we only want one message
\skip on_message
\until }
The message body is a `proton::value`, see the documentation for more on how to
extract the message body as type-safe C++ values.
Our `main` function creates an instance of the `hello_world` handler and a
proton::container using that handler. Calling `proton::container::run` sets
things in motion and returns when we close the connection as there is nothing
further to do. It may throw an exception, which will be a subclass of
`proton::error`. That in turn is a subclass of `std::exception`.
\skip main
\until }
\until }
\until }
Hello World, Direct!
--------------------
\dontinclude helloworld_direct.cpp
Though often used in conjunction with a broker, AMQP does not *require* this. It
also allows senders and receivers to communicate directly if desired.
We will modify our example to send a message directly to itself. This is a bit
contrived but illustrates both sides of the direct send/receive scenario. Full
code at \ref helloworld_direct.cpp
The first difference, is that rather than creating a receiver on the same
connection as our sender, we listen for incoming connections by invoking the
`proton::container::listen()` method on the container.
\skip on_start
\until }
As we only need then to initiate one link, the sender, we can do that by
passing in a url rather than an existing connection, and the connection
will also be automatically established for us.
We send the message in response to the `on_sendable()` callback and
print the message out in response to the `on_message()` callback exactly
as before.
\skip on_sendable
\until }
\until }
However we also handle two new events. We now close the connection from
the senders side once the message has been accepted.
The acceptance of the message is an indication of successful transfer to the
peer. We are notified of that event through the `on_delivery_accept()`
callback.
\skip on_delivery_accept
\until }
Then, once the connection has been closed, of which we are
notified through the `on_connection_close()` callback, we stop accepting incoming
connections at which point there is no work to be done and the
event loop exits, and the run() method will return.
\skip on_connection_close
\until }
So now we have our example working without a broker involved!
Note that for this example we pick an "unusual" port 8888 since we are talking
to ourselves rather than a broker.
\skipline url =
Asynchronous Send and Receive
-----------------------------
Of course, these `HelloWorld!` examples are very artificial, communicating as
they do over a network connection but with the same process. A more realistic
example involves communication between separate processes (which could indeed be
running on completely separate machines).
Let's separate the sender from the receiver, and transfer more than a single
message between them.
We'll start with a simple sender \ref simple_send.cpp.
\dontinclude simple_send.cpp
As with the previous example, we define the application logic in a class that
handles events. Because we are transferring more than one message, we need to
keep track of how many we have sent. We'll use a `sent` member variable for
that. The `total` member variable will hold the number of messages we want to
send.
\skip class simple_send
\until total
As before, we use the `on_start()` event to establish our sender link over which
we will transfer messages.
\skip on_start
\until }
AMQP defines a credit-based flow control mechanism. Flow control allows
the receiver to control how many messages it is prepared to receive at a
given time and thus prevents any component being overwhelmed by the
number of messages it is sent.
In the `on_sendable()` callback, we check that our sender has credit
before sending messages. We also check that we haven't already sent the
required number of messages.
\skip on_sendable
\until }
\until }
The `proton::sender::send()` call above is asynchronous. When it returns the
message has not yet actually been transferred across the network to the
receiver. By handling the `on_accepted()` event, we can get notified when the
receiver has received and accepted the message. In our example we use this event
to track the confirmation of the messages we have sent. We only close the
connection and exit when the receiver has received all the messages we wanted to
send.
\skip on_delivery_accept
\until }
\until }
If we are disconnected after a message is sent and before it has been
confirmed by the receiver, it is said to be `in doubt`. We don't know
whether or not it was received. In this example, we will handle that by
resending any in-doubt messages. This is known as an 'at-least-once'
guarantee, since each message should eventually be received at least
once, though a given message may be received more than once (i.e.
duplicates are possible). In the `on_disconnected()` callback, we reset
the sent count to reflect only those that have been confirmed. The
library will automatically try to reconnect for us, and when our sender
is sendable again, we can restart from the point we know the receiver
got to.
\skip on_disconnect
\until }
\dontinclude simple_recv.cpp
Now let's look at the corresponding receiver \ref simple_recv.cpp
This time we'll use an `expected` member variable for for the number of messages we expect and
a `received` variable to count how many we have received so far.
\skip class simple_recv
\until received
We handle `on_start()` by creating our receiver, much like we
did for the sender.
\skip on_start
\until }
We also handle the `on_message()` event for received messages and print the
message out as in the `Hello World!` examples. However we add some logic to
allow the receiver to wait for a given number of messages, then to close the
connection and exit. We also add some logic to check for and ignore duplicates,
using a simple sequential id scheme.
\skip on_message
\until }
Direct Send and Receive
-----------------------
Sending between these two examples requires an intermediary broker since neither
accepts incoming connections. AMQP allows us to send messages directly between
two processes. In that case one or other of the processes needs to accept
incoming connections. Let's create a modified version of the receiving example
that does this with \ref direct_recv.cpp
\dontinclude direct_recv.cpp
There are only two differences here. Instead of initiating a link (and
implicitly a connection), we listen for incoming connections.
\skip on_start
\until }
When we have received all the expected messages, we then stop listening for
incoming connections by closing the acceptor object.
\skip on_message
\until }
\until }
\until }
\until }
You can use the \ref simple_send.cpp example to send to this receiver
directly. (Note: you will need to stop any broker that is listening on the 5672
port, or else change the port used by specifying a different address to each
example via the -a command line switch).
We can also modify the sender to allow the original receiver to connect to it,
in \ref direct_send.cpp. Again that just requires two modifications:
\dontinclude direct_send.cpp
As with the modified receiver, instead of initiating establishment of a
link, we listen for incoming connections.
\skip on_start
\until }
When we have received confirmation of all the messages we sent, we can
close the acceptor in order to exit.
\skip on_delivery_accept
\until }
\until }
To try this modified sender, run the original \ref simple_recv.cpp against it.
The symmetry in the underlying AMQP that enables this is quite unique and
elegant, and in reflecting this the proton API provides a flexible toolkit for
implementing all sorts of interesting intermediaries (\ref broker.hpp and \ref
broker.cpp provide a simple broker for testing purposes is an example of this).
Request/Response
----------------
A common pattern is to send a request message and expect a response message in
return. AMQP has special support for this pattern. Let's have a look at a simple
example. We'll start with \ref server.cpp, the program that will process the
request and send the response. Note that we are still using a broker in this
example.
Our server will provide a very simple service: it will respond with the
body of the request converted to uppercase.
\dontinclude server.cpp
\skip class server
\until };
The code here is not too different from the simple receiver example. When we
receive a request in `on_message` however, we look at the
`proton::message::reply_to` address and create a sender with that address for
the response. We'll cache the senders incase we get further requests with the
same `reply_to`.
Now let's create a simple \ref client.cpp to test this service out.
\dontinclude client.cpp
Our client takes a list of strings to send as requests
\skipline client(
Since we will be sending and receiving, we create a sender and a receiver in
`on_start`. Our receiver has a blank address and sets the `dynamic` flag to
true, which means we expect the remote end (broker or server) to assign a unique
address for us.
\skip on_start
\until }
Now a function to send the next request from our list of requests. We set the
reply_to address to be the dynamically assigned address of our receiver.
\skip send_request
\until }
We need to use the address assigned by the broker as the `reply_to` address of
our requests, so we can't send them until our receiver has been set up. To do
that, we add an `on_link_open()` method to our handler class, and if the link
associated with event is the receiver, we use that as the trigger to send our
first request.
\skip on_link_open
\until }
When we receive a reply, we send the next request.
\skip on_message
\until }
\until }
\until }
Direct Request/Response
-----------------------
We can avoid the intermediary process by writing a server that accepts
connections directly, \ref server_direct.cpp. It involves the following changes
to our original server:
\dontinclude server_direct.cpp
Our server must generate a unique reply-to addresses for links from the
client that request a dynamic address (previously this was done by the broker.)
We use a simple counter.
\skip generate_address
\until }
Next we need to handle incoming requests for links with dynamic addresses from
the client. We give the link a unique address and record it in our `senders`
map.
\skip on_link_open
\until }
Note we are interested in *sender* links above because we are implementing the
server. A *receiver* link created on the client corresponds to a *sender* link
on the server.
Finally when we receive a message we look up its `reply_to` in our senders map and send the reply.
\skip on_message
\until }
\until }
\until }
Connection Engine
-----------------
The `proton::connection_engine` is an alternative to the container. For simple
applications with a single connection, its use is about the same as the the
`proton::container`, but it allows more flexibility for multi-threaded
applications or applications with unusual IO requirements.
\dontinclude engine/helloworld.cpp
We'll look at the \ref engine/helloworld.cpp example step-by-step to see how it differs
from the container \ref helloworld.cpp version.
First we include the `proton::io::socket_engine` class, which is a `proton::connection_engine`
that uses socket IO.
\skipline proton/io.hpp
Our `hello_world` class differs only in the `on_start()` method. Instead of
calling `container.connect()`, we simply call `proton::connection::open` to open the
engine's' connection:
\skip on_start
\until }
Our `main` function only differs in that it creates and runs a `socket_engine`
instead of a `container`.
\skip main
\until }
\until }
\until }
*/