Apache Qpid: Open Source AMQP Messaging - .NET User Guide
Tutorial
This tutorial consists of a series of examples using the three
most commonly used exchange types - Direct, Fanout and
Topic
exchanges. These examples show how to write applications that use
the most common messaging paradigms.
direct
In the direct examples, a message producer writes to the direct
exchange, specifying a routing key. A message consumer reads
messages from a named queue. This illustrates clean separation
of concerns - message producers need to know only the exchange
and the routing key, message consumers need to know only which
queue to use on the broker.
fanout
The fanout examples use a fanout exchange and do not use
routing keys. Each binding specifies that all messages for a
given exchange should be delivered to a given queue.
pub-sub
In the publish/subscribe examples, a publisher
application writes messages to an exchange, specifying a
multi-part key. A subscriber application subscribes to
messages that match the relevant parts of these keys, using a
private queue for each subscription.
request-response
In the request/response examples, a simple service accepts
requests from clients and sends responses back to them. Clients
create their own private queues and corresponding routing keys.
When a client sends a request to the server, it specifies its
own routing key in the reply-to field of the request. The
server uses the client's reply-to field as the routing key for
the response.
Running the
Examples
Before running the examples, you need to unzip the file
Qpid.NET-net-2.0-M4.zip, the following tree is created:
<home>
|-qpid
|-lib (contains the required dlls)
|-examples
|- direct
| |-example-direct-Listener.exe
| |-example-direct-Producer.exe
|- fanout
| |-example-fanout-Listener.exe
| |-example-fanout-Producer.exe
|- pub-sub
| |-example-pub-sub-Listener.exe
| |-example-pub-sub-Publisher.exe
|- request-response
|-example-request-response-Client.exe
|-example-request-response-Server.exe
Make sure your PATH contains the directory
<home>/qpid/lib
The examples can be run by executing the provided exe files:
$ cd <home>/qpid/examples/examplefolder
$ example-...-.exe [hostname] [portnumber]
where [hostname] is the qpid broker host name
(default is localhost) and [portnumber] is the port number on which the
qpid broker is accepting connection (default is 5672).
Creating
and Closing Sessions
All of the examples have been written using the Apache Qpid .NEt
0.10 API. The examples use the same skeleton code to initialize
the program, create a session, and clean up before exiting:
using System;
using System.IO;
using System.Text;
using System.Threading;
using org.apache.qpid.client;
using org.apache.qpid.transport;
...
private static void Main(string[] args)
{
string host = args.Length > 0 ? args[0] : "localhost";
int port = args.Length > 1 ? Convert.ToInt32(args[1]) : 5672;
Client connection = new Client();
try
{
connection.connect(host, port, "test", "guest", "guest");
ClientSession session = connection.createSession(50000);
//--------- Main body of program --------------------------------------------
connection.close();
}
catch (Exception e)
{
Console.WriteLine("Error: \n" + e.StackTrace);
}
}
...
Writing
Direct Applications
This section describes two programs that implement direct
messaging using a Direct exchange:
• org.apache.qpid.example.direct.Producer (from
example-direct-producer) publishes messages to the amq.direct
exchange, using the routing key routing_key.
•org.apache.qpid.example.direct.Listener (from
example-direct-Listener) uses a message listener to receive
messages from the queue named message_queue.
Running the
Direct Examples
1) Make sure your PATH contains the directory
<home>/qpid/lib
2) Make sure that a qpid broker is running:
$ ps -eaf | grep qpidd
If a broker is running, you should see the qpidd process in the
output of the above
command.
3) Read the messages from the message queue using direct
listener, as follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-direct-Listener.exe [hostname] [portnumber]
or with mono:
$ mono ./example-direct-Listener.exe [hostname] [portnumber]
This program is waiting for messages to be published, see next
step:
4) Publish a series of messages to the amq.direct exchange by
running direct producer, as follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-direct-Producer.exe [hostname] [portnumber]
or with mono:
$ mono ./example-direct-Producer.exe [hostname] [portnumber]
This program has no output; the messages are routed to the
message queue, as instructed by the binding.
5) Go to the windows where you are running your listener. You
should see the following output:
Message: Message 0
Message: Message 1
Message: Message 2
Message: Message 3
Message: Message 4
Message: Message 5
Message: Message 6
Message: Message 7
Message: Message 8
Message: Message 9
Message: That's all, folks!
Now we will examine the code for each of these programs. In each
section, we will discuss only
the code that must be added to the skeleton shown in Section
"Creating and Closing Sessions".
Reading
Messages from the Queue
The program , listener.cs, is a message listener that receives
messages from a queue.
First it creates a queue named message_queue, then binds it to
the amq.direct exchange using the binding key routing_key.
//--------- Main body of program --------------------------------------------
// Create a queue named "message_queue", and route all messages whose
// routing key is "routing_key" to this newly created queue.
session.queueDeclare("message_queue");
session.exchangeBind("message_queue", "amq.direct", "routing_key");
The queue created by this program continues to exist after the
program exits, and any message whose routing key matches the key
specified in the binding will be routed to the corresponding
queue by the broker. Note that the queue could have been be
deleted using the following code:
session.queueDelete("message_queue");
To create a message listener, create a class derived from
IMessageListener, and override the messageTransfer method,
providing the code that should be executed when a message is
received.
public class MessageListener : IMessageListener
{
......
public void messageTransfer(IMessage m)
{
.....
}
The main body of the program creates a listener for the
subscription; attaches the listener to a message queue; and
subscribe to the queue to receive messages from the queue.
lock (session)
{
// Create a listener and subscribe it to the queue named "message_queue"
IMessageListener listener = new MessageListener(session);
session.attachMessageListener(listener, "message_queue");
session.messageSubscribe("message_queue");
// Receive messages until all messages are received
Monitor.Wait(session);
}
The MessageListener's messageTransfer() function is called
whenever a message is received. In this example the message is
printed and tested to see if it is the final message. Once the
final message is received, the messages are acknowledged.
BinaryReader reader = new BinaryReader(m.Body, Encoding.UTF8);
byte[] body = new byte[m.Body.Length - m.Body.Position];
reader.Read(body, 0, body.Length);
ASCIIEncoding enc = new ASCIIEncoding();
string message = enc.GetString(body);
Console.WriteLine("Message: " + message);
// Add this message to the list of message to be acknowledged
_range.add(m.Id);
if( message.Equals("That's all, folks!") )
{
// Acknowledge all the received messages
_session.messageAccept(_range);
lock(_session)
{
Monitor.Pulse(_session);
}
}
Publishing
Messages to a Direct Exchange
The second program in the direct example, Producer.cs, publishes
messages to the amq.direct exchange using the routing key
routing_key.
First, create a message and set a routing key. The same routing
key will be used for each message we send, so you only need to
set this property once.
IMessage message = new Message();
// The routing key is a message property. We will use the same
// routing key for each message, so we'll set this property
// just once. (In most simple cases, there is no need to set
// other message properties.)
message.DeliveryProperties.setRoutingKey("routing_key");
Now send some messages:
// Asynchronous transfer sends messages as quickly as
// possible without waiting for confirmation.
for (int i = 0; i < 10; i++)
{
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("Message " + i));
session.messageTransfer("amq.direct", message);
}
Send a final synchronous message to indicate termination:
// And send a syncrhonous final message to indicate termination.
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("That's all, folks!"));
session.messageTransfer("amq.direct", "routing_key", message);
session.sync();
Writing
Fanout Applications
This section describes two programs that illustrate the use of a
Fanout exchange.
Listener.cs makes a unique queue private for each instance of
the listener, and binds that queue to the fanout exchange. All
messages sent to the fanout exchange are delivered to each
listener's queue.
Producer.cs publishes messages to the fanout exchange. It
does not use a routing key, which is not needed by the fanout
exchange.
Running the
Fanout Examples
1) Make sure your PATH contains the directory
<home>/qpid/lib
2) Make sure that a qpid broker is running:
$ ps -eaf | grep qpidd
If a broker is running, you should see the qpidd process in the
output of the above
command.
3) In separate windows, start one or more fanout listeners as
follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-fanout-Listener.exe [hostname] [portnumber]
or with mono:
$ mono ./example-fanout-Listener.exe [hostname] [portnumber]
The listener creates a private queue, binds it to the amq.fanout
exchange, and waits for messages to arrive on the queue. When the
listener starts, you will see the following message:
Listening
This program is waiting for messages to be published, see next
step:
4) In a separate window, publish a series of messages to the
amq.fanout exchange by running fanout producer, as follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-fanout-Producer.exe [hostname] [portnumber]
or with mono:
$ mono ./example-fanout-Producer.exe [hostname] [portnumber]
This program has no output; the messages are routed to the
message queue, as prescribed by the binding.
5) Go to the windows where you are running listeners. You should
see the following output for each listener:
Message: Message 0
Message: Message 1
Message: Message 2
Message: Message 3
Message: Message 4
Message: Message 5
Message: Message 6
Message: Message 7
Message: Message 8
Message: Message 9
Message: That's all, folks!
Now we will examine the code for each of these programs. In each
section, we will discuss only
the code that must be added to the skeleton shown in Section
"Creating and Closing Sessions".
Consuming from a
Fanout Exchange
The first program in the fanout example, Listener.cs, creates a
private queue, binds it to the amq.fanout exchange, and waits for
messages to arrive on the queue, printing them out as they
arrive. It uses a Listener that is identical to the one used in
the direct example:
public class MessageListener : IMessageListener
{
private readonly ClientSession _session;
private readonly RangeSet _range = new RangeSet();
public MessageListener(ClientSession session)
{
_session = session;
}
public void messageTransfer(IMessage m)
{
BinaryReader reader = new BinaryReader(m.Body, Encoding.UTF8);
byte[] body = new byte[m.Body.Length - m.Body.Position];
reader.Read(body, 0, body.Length);
ASCIIEncoding enc = new ASCIIEncoding();
string message = enc.GetString(body);
Console.WriteLine("Message: " + message);
// Add this message to the list of message to be acknowledged
_range.add(m.Id);
if (message.Equals("That's all, folks!"))
{
// Acknowledge all the received messages
_session.messageAccept(_range);
lock (_session)
{
Monitor.Pulse(_session);
}
}
}
}
The listener creates a private queue to receive its messages and
binds it to the fanout exchange:
string myQueue = session.Name;
session.queueDeclare(myQueue, Option.EXCLUSIVE, Option.AUTO_DELETE);
session.exchangeBind(myQueue, "amq.fanout", "my-key");
Now we create a listener and subscribe it to the queue:
lock (session)
{
Console.WriteLine("Listening");
// Create a listener and subscribe it to my queue.
IMessageListener listener = new MessageListener(session);
session.attachMessageListener(listener, myQueue);
session.messageSubscribe(myQueue);
// Receive messages until all messages are received
Monitor.Wait(session);
}
Publishing
Messages to the Fanout Exchange
The second program in this example, Producer.cs, writes messages
to the fanout queue.
// Unlike topic exchanges and direct exchanges, a fanout
// exchange need not set a routing key.
IMessage message = new Message();
// Asynchronous transfer sends messages as quickly as
// possible without waiting for confirmation.
for (int i = 0; i < 10; i++)
{
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("Message " + i));
session.messageTransfer("amq.fanout", message);
}
// And send a syncrhonous final message to indicate termination.
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("That's all, folks!"));
session.messageTransfer("amq.fanout", message);
session.sync();
Writing
Publish/Subscribe Applications
This section describes two programs that implement
Publish/Subscribe messaging using a topic exchange.
• Publisher.cS sends messages to the amq.topic exchange,
using the multipart routing keys usa.news, usa.weather,
europe.news, and europe.weather.
• Listener.cs creates private queues for news, weather,
usa, and europe, binding them to the amq.topic exchange using
bindings that match the corresponding parts of the multipart
routing keys.
In this example, the publisher creates messages for topics like
news, weather, and sports that happen in regions like Europe,
Asia, or the United States. A given consumer may be interested in
all weather messages, regardless of region, or it may be
interested in news and weather for the United States, but
uninterested in items for other regions. In this example, each
consumer sets up its own private queues, which receive precisely
the messages that particular consumer is interested in.
Running
the Publish-Subscribe Examples
1) Make sure your PATH contains the directory
<home>/qpid/lib
2) Make sure that a qpid broker is running:
$ ps -eaf | grep qpidd
If a broker is running, you should see the qpidd process in the
output of the above
command.
3) In separate windows, start one or more topic subscribers as
follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-pub-sub--Listener.exe [hostname] [portnumber]
or with mono:
$ mono ./example-pub-sub-Listener.exe [hostname] [portnumber]
You will see output similar to this:
Listening for messages ...
Declaring queue: usa
Declaring queue: europe
Declaring queue: news
Declaring queue: weather
Each topic consumer creates a set of private queues, and binds
each queue to the amq.topic exchange together with a binding that
indicates which messages should be routed to the queue.
4) In another window, start the topic publisher, which publishes
messages to the amq.topic exchange, as follows:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-pub-sub-Producer.exe [hostname] [portnumber]
or with mono:
$ mono ./example-pub-sub-Producer.exe [hostname] [portnumber]
This program has no output; the messages are routed to the
message queues for each topic_consumer as specified by the
bindings the consumer created.
5) Go back to the window for each topic consumer. You should see
output like this:
Message: Message 0 from usa
Message: Message 0 from news
Message: Message 0 from weather
Message: Message 1 from usa
Message: Message 1 from news
Message: Message 2 from usa
Message: Message 2 from news
Message: Message 3 from usa
Message: Message 3 from news
Message: Message 4 from usa
Message: Message 4 from news
Message: Message 5 from usa
Message: Message 5 from news
Message: Message 6 from usa
Message: Message 6 from news
Message: Message 7 from usa
Message: Message 7 from news
Message: Message 8 from usa
Message: Message 8 from news
Message: Message 9 from usa
....
Message: That's all, folks! from weather
Shutting down listener for control
Message: That's all, folks! from europe
Shutting down listener for control
Now we will examine the code for each of these programs. In each
section, we will discuss only
the code that must be added to the skeleton shown in Section
"Creating and Closing Sessions".
Publishing
Messages to a Topic Exchange
The first program in the publish/subscribe example, Publisher.cs,
defines two new functions: one that publishes messages to the
topic exchange, and one that indicates that no more messages are
coming.
The publishMessages function publishes a series of five messages
using the specified routing key.
private static void publishMessages(ClientSession session, string routing_key)
{
IMessage message = new Message();
// Asynchronous transfer sends messages as quickly as
// possible without waiting for confirmation.
for (int i = 0; i < 10; i++)
{
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("Message " + i));
session.messageTransfer("amq.topic", routing_key, message);
}
}
The noMoreMessages function signals the end of messages using the
control routing key, which is reserved for control messages.
private static void noMoreMessages(ClientSession session)
{
IMessage message = new Message();
// And send a syncrhonous final message to indicate termination.
message.clearData();
message.appendData(Encoding.UTF8.GetBytes("That's all, folks!"));
session.messageTransfer("amq.topic", "control", message);
session.sync();
}
In the main body of the program, messages are published using
four different routing keys, and then the end of messages is
indicated by a message sent to a separate routing key.
publishMessages(session, "usa.news");
publishMessages(session, "usa.weather");
publishMessages(session, "europe.news");
publishMessages(session, "europe.weather");
noMoreMessages(session);
Reading
Messages from the Queue
The second program in the publish/subscribe example, Listener.cs,
creates a local private queue, with a unique name, for each of
the four binding keys it specifies: usa.#, europe.#, #.news, and
#.weather, and creates a listener.
Console.WriteLine("Listening for messages ...");
// Create a listener
prepareQueue("usa", "usa.#", session);
prepareQueue("europe", "europe.#", session);
prepareQueue("news", "#.news", session);
prepareQueue("weather", "#.weather", session);
The prepareQueue() method creates a queue using a queue name and
a routing key supplied as arguments it then attaches a listener
with the session for the created queue and subscribe for this
receiving messages from the queue:
// Create a unique queue name for this consumer by concatenating
// the queue name parameter with the Session ID.
Console.WriteLine("Declaring queue: " + queue);
session.queueDeclare(queue, Option.EXCLUSIVE, Option.AUTO_DELETE);
// Route messages to the new queue if they match the routing key.
// Also route any messages to with the "control" routing key to
// this queue so we know when it's time to stop. A publisher sends
// a message with the content "That's all, Folks!", using the
// "control" routing key, when it is finished.
session.exchangeBind(queue, "amq.topic", routing_key);
session.exchangeBind(queue, "amq.topic", "control");
// subscribe the listener to the queue
IMessageListener listener = new MessageListener(session);
session.attachMessageListener(listener, queue);
session.messageSubscribe(queue);
Writing
Request/Response Applications
In the request/response examples, we write a server that accepts
strings from clients and converts them to upper case, sending the
result back to the requesting client. This example consists of
two programs.
Client.cs is a client application that sends messages to the
server.
• Server.cs is a service that accepts messages, converts
their content to upper case, and sends the result to the
amq.direct exchange, using the request's reply-to property as
the routing key for the response.
Running
the Request/Response Examples
1) Make sure your PATH contains the directory
<home>/qpid/lib
2) Make sure that a qpid broker is running:
$ ps -eaf | grep qpidd
If a broker is running, you should see the qpidd process in the
output of the above
command.
3) Run the server.
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-request-response-Server.exe [hostname] [portnumber]
or with mono:
$ mono ./example-request-response-Server.exe [hostname] [portnumber]
You will see output similar to this:
Waiting for requests
4) In a separate window, start a client:
$ cd <home>/qpid/examples/direct
With cygwin:
$ ./example-request-response-Client.exe [hostname] [portnumber]
or with mono:
$ mono ./example-request-response-Client.exe [hostname] [portnumber]
You will see output similar to this:
Activating response queue listener for: clientSystem.Byte[]
Waiting for all responses to arrive ...
Response: TWAS BRILLIG, AND THE SLITHY TOVES
Response: DID GIRE AND GYMBLE IN THE WABE.
Response: ALL MIMSY WERE THE BOROGROVES,
Response: AND THE MOME RATHS OUTGRABE.
Shutting down listener for clientSystem.Byte[]
Response: THAT'S ALL, FOLKS!
4) Go back to the server window, the output should be similar to
this:
Waiting for requests
Request: Twas brillig, and the slithy toves
Request: Did gire and gymble in the wabe.
Request: All mimsy were the borogroves,
Request: And the mome raths outgrabe.
Request: That's all, folks!
Now we will examine the code for each of these programs. In each
section, we will discuss only the code that must be added to the
skeleton shown in Section "Creating and Closing Sessions".
The Client
Application
The first program in the request-response example, Client.cs,
sets up a private response queue to receive responses from the
server, then sends messages the server, listening to the response
queue for the server's responses.
string response_queue = "client" + session.getName();
// Use the name of the response queue as the routing key
session.queueDeclare(response_queue);
session.exchangeBind(response_queue, "amq.direct", response_queue);
// Create a listener for the response queue and listen for response messages.
Console.WriteLine("Activating response queue listener for: " + response_queue);
IMessageListener listener = new ClientMessageListener(session);
session.attachMessageListener(listener, response_queue);
session.messageSubscribe(response_queue);
Set some properties that will be used for all requests. The
routing key for a request is request.
The reply-to property is set to the routing key for the client's
private queue.
IMessage request = new Message();
request.DeliveryProperties.setRoutingKey("request");
request.MessageProperties.setReplyTo(new ReplyTo("amq.direct", response_queue));
Now send some requests...
string[] strs = {
"Twas brillig, and the slithy toves",
"Did gire and gymble in the wabe.",
"All mimsy were the borogroves,",
"And the mome raths outgrabe.",
"That's all, folks!"
};
foreach (string s in strs)
{
request.clearData();
request.appendData(Encoding.UTF8.GetBytes(s));
session.messageTransfer("amq.direct", request);
}
And wait for responses to arrive:
Console.WriteLine("Waiting for all responses to arrive ...");
Monitor.Wait(session);
The Server
Application
The second program in the request-response example, Server.cs,
uses the reply-to property as the routing key for responses.
The main body of Server.cs creates an exclusive queue for
requests, then waits for messages to arrive.
const string request_queue = "request";
// Use the name of the request queue as the routing key
session.queueDeclare(request_queue);
session.exchangeBind(request_queue, "amq.direct", request_queue);
lock (session)
{
// Create a listener and subscribe it to the request_queue
IMessageListener listener = new MessageListener(session);
session.attachMessageListener(listener, request_queue);
session.messageSubscribe(request_queue);
// Receive messages until all messages are received
Console.WriteLine("Waiting for requests");
Monitor.Wait(session);
}
The listener's messageTransfer() method converts the request's
content to upper case, then sends a response to the broker, using
the request's reply-to property as the routing key for the
response.
BinaryReader reader = new BinaryReader(request.Body, Encoding.UTF8);
byte[] body = new byte[request.Body.Length - request.Body.Position];
reader.Read(body, 0, body.Length);
ASCIIEncoding enc = new ASCIIEncoding();
string message = enc.GetString(body);
Console.WriteLine("Request: " + message);
// Transform message content to upper case
string responseBody = message.ToUpper();
// Send it back to the user
response.clearData();
response.appendData(Encoding.UTF8.GetBytes(responseBody));
_session.messageTransfer("amq.direct", routingKey, response);