Updated README

This commit is contained in:
Martijn Otto 2014-04-10 13:50:24 +02:00
parent d2c17869e0
commit 3348e2881c
1 changed files with 145 additions and 189 deletions

170
README.md
View File

@ -182,15 +182,10 @@ and to publish and consume messages. You can best take a look at the channel.h
C++ header file for a list of all available methods. Every method in it is well
documented.
The constructor of the Channel object accepts two parameters: the connection object,
and a pointer to a ChannelHandler object. In the example we did
not use this ChannelHandler object. However, in normal circumstances, you should
always pass a pointer to a ChannelHandler object every time you construct a channel.
Just like the ConnectionHandler class, the ChannelHandler class is a base class that
you can extend to override the virtual methods you need. The AMQP library
will call these methods to inform you that an operation on the channel has succeeded
or has failed.
The constructor of the Channel object accepts one parameter: the connection object.
Unlike the connection it does not accept a handler. Instead of this (almost) every
function in the channel returns a Deferred object. This deferred object can be used
to install handlers to be called in case of success, failure or in either case.
For example, if you call the channel.declareQueue() method, the AMQP-CPP library will
send a message to the RabbitMQ message broker to ask it to declare the
@ -198,8 +193,19 @@ queue. However, because all operations in the library are asynchronous, the
declareQueue() method immediately returns 'true', although it is at that time
not yet known whether the queue was correctly declared. Only after a while,
after the instruction has reached the server, and the confirmation from the server
has been sent back to the client, your ChannelHandler::onQueueDeclared()
method will be called to inform you that the operation was succesful.
has been sent back to the client, your handler method will be called to inform
you that the operation was succesful.
````c++
Channel myChannel(&connection);
myChannel.declareQueue("my-queue")
.onSuccess([](AMQP::Channel *channel, const std::string& name, uint32_t messageCount, uint32_t consumerCount) {
// by now the queue is created
})
.onError([](AMQP::Channel *channel, const std::string message) {
// something went wrong creating the channel
});
````
It is important to realize that any error that occurs on a channel, will
invalidate the entire channel,. including all subsequent instructions that
@ -212,7 +218,7 @@ myChannel.declareQueue("my-queue");
myChannel.declareExchange("my-exchange");
````
If the first declareQueue() call fails in the example above, your ChannelHandler::onError()
If the first declareQueue() call fails in the example above, your Deferred::onError()
method will be called after a while to report this failure. And although the
second instruction to declare an exchange has already been sent to the server, it will be
ignored because the channel was already in an invalid state after the first failure.
@ -223,7 +229,7 @@ You can overcome this by using multiple channels:
Channel channel1(connection, &myHandler);
Channel channel2(connection, &myHandler);
channel1.declareQueue("my-queue");
channel2.declareQueue("my-exchange");
channel2.declareExchange("my-exchange");
````
Now, if an error occurs with declaring the queue, it will not have
@ -233,51 +239,6 @@ RabbitMQ server, so some extra bytes are sent over the network,
and some additional resources in both the client application and the
RabbitMQ server are used (although this is all very limited).
Let's get back to the ChannelHandler class. It has many methods that you can
implement - all of which are optional. All methods in it have a default empty implementation,
so you can choose to only override the ones that you are interested in. When you're
writing a consumer application for example, you probably are only interested in
errors that occur, and in incoming messages:
````c++
#include <amqpcpp.h>
class MyChannelHandler : public AMQP::ChannelHandler
{
public:
/**
* Method that is called when an error occurs on the channel, and
* the channel ends up in an error state
* @param channel the channel on which the error occured
* @param message human readable error message
*/
virtual void onError(AMQP::Channel *channel, const std::string &message)
{
// @todo
// do something with the error message (like reporting it to the end-user)
// and destruct the channel object because it now no longer is usable
}
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered)
{
// @todo
// do something with the incoming message
}
};
````
FLAGS AND TABLES
================
@ -304,12 +265,12 @@ tables are used by many methods.
* @param flags combination of flags
* @param arguments optional arguments
*/
bool declareQueue(const std::string &name, int flags, const AMQP::Table &arguments);
bool declareQueue(const std::string &name, const AMQP::Table &arguments);
bool declareQueue(const std::string &name, int flags = 0);
bool declareQueue(int flags, const AMQP::Table &arguments);
bool declareQueue(const AMQP::Table &arguments);
bool declareQueue(int flags = 0);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, int flags, const Table &arguments);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, const Table &arguments);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, int flags = 0);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(int flags, const Table &arguments);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const Table &arguments);
Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(int flags = 0);
````
As you can see, the method comes in many forms, and it is up to you to choose
@ -396,18 +357,29 @@ bool publish(const std::string &exchange, const std::string &routingKey, int fla
bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size);
````
Published messages are normally not confirmed by the server, hence there is no
ChannelHandler::onPublished() method that you can implement to find out if
a message was correctly received by the server. That's by design in the
AMQP protocol, to not unnecessarily slow down message publishing. As long
as no error is reported via the ChannelHandler::onError() method, you can safely
Published messages are normally not confirmed by the server, therefore the publish method does not
return a deferred. That's by design in the AMQP protocol, to not unnecessarily slow down message
publishing. As long as no error is reported via the ChannelHandler::onError() method, you can safely
assume that your messages were delivered.
If you use the flags parameter to set either the option 'mandatory' or
'immediate', a message that could not be routed or directly delivered to a consumer
is sent back to the client, and ends up in the ChannelHandler::onReturned()
method. At the time of this writing however, the 'immediate' option does not
seem to be supported by RabbitMQ.
This can of course be a problem when you are publishing many messages. If you get an error halfway through
there is no way to know for sure how many messages made it to the broker and how many should be republished.
If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs,
the transaction is automatically rolled back by RabbitMQ and none of the messages are actually published.
````c++
channel.startTransaction();
channel.publish("my-exchange", "my-key", "my first message");
channel.publish("my-exchange", "my-key", "another message");
channel.commitTransaction()
.onSuccess([](AMQP::Channel *channel) {
// all messages were successfully published
})
.onError([](AMQP::Channel *channel) {
// none of the messages were published
// now we have to do it all over again
});
````
CONSUMING MESSAGES
@ -454,45 +426,28 @@ The full documentation from the C++ Channel.h headerfile looks like this:
* @param arguments additional arguments
* @return bool
*/
bool consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments);
bool consume(const std::string &queue, const std::string &tag, int flags = 0);
bool consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments);
bool consume(const std::string &queue, int flags, const AMQP::Table &arguments);
bool consume(const std::string &queue, int flags = 0);
bool consume(const std::string &queue, const AMQP::Table &arguments);
DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags, const AMQP::Table &arguments);
DeferredConsumer& consume(const std::string &queue, const std::string &tag, int flags = 0);
DeferredConsumer& consume(const std::string &queue, const std::string &tag, const AMQP::Table &arguments);
DeferredConsumer& consume(const std::string &queue, int flags, const AMQP::Table &arguments);
DeferredConsumer& consume(const std::string &queue, int flags = 0);
DeferredConsumer& consume(const std::string &queue, const AMQP::Table &arguments);
````
In your ChannelHandler you can override the onConsumerStarted() method, that will be
first called before any messages are sent to you. Most users choose not to override this
method, because there is not much useful to do in it. After the consumer has started, however,
messages are starting to be sent from RabbitMQ to your client application, and they are all
passed to the ChannelHandler::onReceived() method. This method is thus very important to implement.
As you can see, the consume method returns a DeferredConsumer. This object is a regular Deferred, with the
addition of the onReceived method. This method can be used to retrieve incoming messages after consumption
has begun.
````c++
class MyChannelHandler : public AMQP::ChannelHandler
{
/**
* Method that is called when a message has been received on a channel
* This message will be called for every message that is received after
* you started consuming. Make sure you acknowledge the messages when its
* safe to remove them from RabbitMQ (unless you set no-ack option when you
* started the consumer)
* @param channel the channel on which the consumer was started
* @param message the consumed message
* @param deliveryTag the delivery tag, you need this to acknowledge the message
* @param consumerTag the consumer identifier that was used to retrieve this message
* @param redelivered is this a redelivered message?
*/
virtual void onReceived(AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered)
{
// @todo
// add your own processing
channel.consume("my-queue").onReceived([](AMQP::Channel *channel, const AMQP::Message &message, uint64_t deliveryTag, const std::string &consumerTag, bool redelivered) {
// @todo
// add your own processing
// after the message was processed, acknowledge it
channel->ack(deliveryTag);
}
}
// after the message was processed, acknowledge it
channel->ack(deliveryTag);
});
````
The Message object holds all information of the delivered message: the actual content,
@ -534,6 +489,7 @@ need additional attention:
- ability to set up secure connections (or is this fully done on the IO level)
- login with other protocols than login/password
- publish confirms
- returned messages
We also need to add more safety checks so that strange or invalid data from
RabbitMQ does not break the library (although in reality RabbitMQ only sends