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 C++ header file for a list of all available methods. Every method in it is well
documented. documented.
The constructor of the Channel object accepts two parameters: the connection object, The constructor of the Channel object accepts one parameter: the connection object.
and a pointer to a ChannelHandler object. In the example we did Unlike the connection it does not accept a handler. Instead of this (almost) every
not use this ChannelHandler object. However, in normal circumstances, you should function in the channel returns a Deferred object. This deferred object can be used
always pass a pointer to a ChannelHandler object every time you construct a channel. to install handlers to be called in case of success, failure or in either case.
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.
For example, if you call the channel.declareQueue() method, the AMQP-CPP library will 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 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 declareQueue() method immediately returns 'true', although it is at that time
not yet known whether the queue was correctly declared. Only after a while, 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 after the instruction has reached the server, and the confirmation from the server
has been sent back to the client, your ChannelHandler::onQueueDeclared() has been sent back to the client, your handler method will be called to inform
method will be called to inform you that the operation was succesful. 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 It is important to realize that any error that occurs on a channel, will
invalidate the entire channel,. including all subsequent instructions that invalidate the entire channel,. including all subsequent instructions that
@ -212,7 +218,7 @@ myChannel.declareQueue("my-queue");
myChannel.declareExchange("my-exchange"); 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 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 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. 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 channel1(connection, &myHandler);
Channel channel2(connection, &myHandler); Channel channel2(connection, &myHandler);
channel1.declareQueue("my-queue"); 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 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 and some additional resources in both the client application and the
RabbitMQ server are used (although this is all very limited). 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 FLAGS AND TABLES
================ ================
@ -304,12 +265,12 @@ tables are used by many methods.
* @param flags combination of flags * @param flags combination of flags
* @param arguments optional arguments * @param arguments optional arguments
*/ */
bool declareQueue(const std::string &name, int flags, const AMQP::Table &arguments); Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, int flags, const Table &arguments);
bool declareQueue(const std::string &name, const AMQP::Table &arguments); Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, const Table &arguments);
bool declareQueue(const std::string &name, int flags = 0); Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const std::string &name, int flags = 0);
bool declareQueue(int flags, const AMQP::Table &arguments); Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(int flags, const Table &arguments);
bool declareQueue(const AMQP::Table &arguments); Deferred<const std::string&, uint32_t, uint32_t>& declareQueue(const Table &arguments);
bool declareQueue(int flags = 0); 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 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); 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 Published messages are normally not confirmed by the server, therefore the publish method does not
ChannelHandler::onPublished() method that you can implement to find out if return a deferred. That's by design in the AMQP protocol, to not unnecessarily slow down message
a message was correctly received by the server. That's by design in the publishing. As long as no error is reported via the ChannelHandler::onError() method, you can safely
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. assume that your messages were delivered.
If you use the flags parameter to set either the option 'mandatory' or This can of course be a problem when you are publishing many messages. If you get an error halfway through
'immediate', a message that could not be routed or directly delivered to a consumer there is no way to know for sure how many messages made it to the broker and how many should be republished.
is sent back to the client, and ends up in the ChannelHandler::onReturned() If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs,
method. At the time of this writing however, the 'immediate' option does not the transaction is automatically rolled back by RabbitMQ and none of the messages are actually published.
seem to be supported by RabbitMQ.
````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 CONSUMING MESSAGES
@ -454,45 +426,28 @@ The full documentation from the C++ Channel.h headerfile looks like this:
* @param arguments additional arguments * @param arguments additional arguments
* @return bool * @return bool
*/ */
bool 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, const AMQP::Table &arguments);
bool consume(const std::string &queue, const std::string &tag, int flags = 0); DeferredConsumer& 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); DeferredConsumer& 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); DeferredConsumer& consume(const std::string &queue, int flags, const AMQP::Table &arguments);
bool consume(const std::string &queue, int flags = 0); DeferredConsumer& 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 AMQP::Table &arguments);
```` ````
In your ChannelHandler you can override the onConsumerStarted() method, that will be As you can see, the consume method returns a DeferredConsumer. This object is a regular Deferred, with the
first called before any messages are sent to you. Most users choose not to override this addition of the onReceived method. This method can be used to retrieve incoming messages after consumption
method, because there is not much useful to do in it. After the consumer has started, however, has begun.
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.
````c++ ````c++
class MyChannelHandler : public AMQP::ChannelHandler 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
* 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
// after the message was processed, acknowledge it // after the message was processed, acknowledge it
channel->ack(deliveryTag); channel->ack(deliveryTag);
} });
}
```` ````
The Message object holds all information of the delivered message: the actual content, 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) - ability to set up secure connections (or is this fully done on the IO level)
- login with other protocols than login/password - login with other protocols than login/password
- publish confirms - publish confirms
- returned messages
We also need to add more safety checks so that strange or invalid data from 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 RabbitMQ does not break the library (although in reality RabbitMQ only sends