You are on page 1of 9

Having fun with Delphi and AMQP

starter expert
COMPONENTS
DEVELOPERS 4
Delphi

In last issue of Blaise Pascal there was a great For now kbmMW supports AMQP v. 0.91
article by Fikret Hasovic, about AMQP (Advanced as a client fully, but in fact knows about all AMQP
Message Queue Protocol), explaining its structure 0.91 definitions so it potentially could also act as an
and some of the historical background about it. AMQP server.
This article is meant to follow up on that article, Components4Developers are considering also
by showing in praxis, how to install one of the supporting AMQP v. 1.0, which is actually in most
more famous AMQP servers (RabbitMQ), and
ways a subset of AMQP 0.91 but with a few changes
connect to it from a Delphi application using the
extensive middleware framework kbmMW
to the transport format. Please check the previous
Enterprise Edition. article by Fikret Hasovic to learn about the
differences between the two versions.
Preword
Some may pose the question if it isn't the case Installing RabbitMQ
that AMQP is a competitor to kbmMW's own Wide First step in anything to do with AMQP,
Information Bus. The short answer is yes: is to install an AMQP 0.91 aware server.
- Both frameworks have been designed for There are multiple to choose from, but one of the
distributing information from publishers to most popular ones, which is both fast and fairly easy
subscribers.
to use, is RabbitMQ.
- Both are built around temporary or persistent
queue based storage for the messages while For this example I only show how to install
they are in transit to the end subscriber. RabbitMQ on Windows. RabbitMQ is also running
- Both have been designed with performance and on Linux and other platforms.
stability in mind. As RabbitMQ is written using the Erlang language,
we need to install the Erlang runtime executables
However they also differ in areas: first.
- AMQP is 100% content agnostic. There is NO http://www.erlang.org/download.html
defined way or standard for how to interpret
Choose, depending on your Windows version, to
the contents of an AMQP message. It has to be
100% agreed between the sender and receiver. download either the 32 bit or 64 bit version of Erlang:
kbmMW have defined types of messages for
different purposes and with defined structure,
but also supports transferring generic content.
- AMQP is only a messaging framework. Nothing
more, nothing less. kbmMW WIB is the
messaging framework of kbmMW, but kbmMW
also contains an application server, database
connectivity, native JSON and XML stacks and
more, and is probably more comparable (in
functionality) to a combo of J2EE, Corba, RMI
and AMQP.
- AMQP is always doing copy/store of messages Run the downloaded file and install Erlang.
to relevant queues, while kbmMW supports
When installed, update your Windows system
reference counting when multiple endpoints
needs to receive a message. environment variables to include the variable
- AMQP natively builds on persistent named ERLANG_HOME.
queues, although it supports temporary queues It must point to the directory containing the Erlang
as well. kbmMW natively builds on shared bin directory.
in/out queues, and optional persistency in (Start > Settings > Control Panel > System >
named queues and thus avoids message Advanced > Environment Variables)
copying as much as possible.

So why AMQP?
Because AMQP is one of the only widely supported
methods of doing messaging between different
architectures and platforms.
As kbmMW is a swiss knife of features,
providing extensive connectivity for Delphi,
C++Builder and FreePascal, its only logical to also O
S
NT S
NE PER
4
P LO
M E
support AMQP as one of the many ways to integrate CO DEV

kbmMW based software with the world.

112 COMPONENTS
DEVELOPERS 4 Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE
Having fun with Delphi and AMQP (Continuation 1)
COMPONENTS
DEVELOPERS 4
Now Erlang is ready for use.
Then download RabbitMQ from here:
http://www.rabbitmq.com
Click the download figure

Then click the quick 5mb Windows download

And run the downloaded installer.


It will quickly install RabbitMQ.
If you have a regular Windows start
menu, you will now see RabbitMQ
in the start menu.

Then add a new system variable. Provide the correct


path to the Erlang installation as the variable value:

Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE


COMPONENTS
DEVELOPERS 4 113
Having fun with Delphi and AMQP (Continuation 2)
COMPONENTS
DEVELOPERS 4

If you are using Windows 8/8.1 without Classic Shell, you can instead
find the shortcuts on the Windows 8 application window.

As can be seen in the shortcuts, it's possible to start For the ease of use, RabbitMQ supports a
RabbitMQ as a service, which means it will stay management console which can be reached via
running after you log of your machine, which is how a Web browser, after having enabled it.
you will want it, the moment you use AMQP in a To do that, start the RabbitMQ command prompt
production environment. via the start menu in Windows.
Ports and numerous other configurations can be In the command prompt type:
made, either by defining a configuration file, or on rabbitmq-plugins enable
Windows by setting a number of environment rabbitmq_management
variables, before starting RabbitMQ. For now we will
run with the default configuration.
However you can read more about how to configure
it here
http://www.rabbitmq.com/configure.html.

114 COMPONENTS
DEVELOPERS 4 Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE
Having fun with Delphi and AMQP (Continuation 3)
COMPONENTS
DEVELOPERS 4
Then start RabbitMQ, by installing it as a service via
the start menu. Start it, also via the start menu.
After it has been started, then RabbitMQ will be
listening on the default TCP port 5672. You can
check that by typing: netstat an | more
Somewhere in the list you will see
TCP 0.0.0.0:5672 if the RabbitMQ server has
been started using the default settings.
As we have enabled a management console, you will
also find RabbitMQ listening on TCP port 15672.
We can access that port via a web browser.
http://localhost:15672

The default username


and password is
guest/guest.
By logging in you will
get to a console where
you can manage
channels, connections,
queues, exchanges and
users. This can be very
handy to use, to check
the status of your
queues while you are
developing. You can
even pop off messages
and save them to a
binary file which you
can check using a fitting
editor (perhaps a hex
editor if your message
bodies are binary).

Now we have RabbitMQ (in its default configuration) running.

Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE


COMPONENTS
DEVELOPERS 4 115
Having fun with Delphi and AMQP (Continuation 4)
COMPONENTS
DEVELOPERS 4
Preparing for a kbmMW client The actual AMQP client implementation is available
It's advisable to change password on the guest user via the class TkbmMWAMQPClient. So we need an
or completely disable it, but only after you have instance of that, for example created in the OnCreate
created a new user with a password which is secret event of the form and deleted in the OnDestroy
to the world, except for to those that needs to access event.
your AMQP server. uses ,kbmMWAMQP,kbmMWAMQPClient
Further, RabbitMQ supports dividing the AMQP
private
server up in departments or virtual hosts, which { Private declarations }
are not able to see each other. So when a AMQP FClient:TkbmMWAMQPClient;
client is allowed to access one virtual hosts, it does
not automatically have access to other virtual hosts.
procedure TForm1.FormCreate(Sender: TObject);
One default virtual host / (slash) is default available.
begin
As it's a good practice to define a custom virtual FClient:=TkbmMWAMQPClient.Create(nil);
host, and users with strong passwords and not rely end;
on the default ones, we will do that now, using the
procedure TForm1.FormDestroy(Sender: TObject);
RabbitMQ command prompt.
begin
To create a user with the username user1 and FClient.Free;
password pwd123 (please choose better passwords and end;
user names) type the following in the RabbitMQ
Next step is to be able to connect that to an AMQP
command prompt:
rabbitmqctl add_user user1 pwd123 server. Let's write some code for the Connect and
Disconnect buttons.
Then we need to create a virtual host. Let's call it procedure TForm1.btnConnectClick(Sender: TObject);
kbmmw. Type the following: begin
rabbitmqctl add_vhost kbmmw FClient.Connect(
'192.168.1.103','user1','pwd123','kbmmw');
end;
And finally we need to add rights for the user user1
to access the virtual host kbmmw. For now we grant procedureTForm1.btnDisconnectClick(Sender:TObject);
full rights (including create and destroy queues, begin
FClient.Disconnect;
exchanges) within the virtual host.
end;
rabbitmqctl set_permissions -p kbmmw
user1 ".*" ".*" ".*"
You can read more about access control here:
https://www.rabbitmq.com/access-
control.html Now we are ready to build the
kbmMW AMQP client.

The kbmMW AMQP client


I'll show an example Delphi based AMQP client
using kbmMW. First lets create the user interface:

The circled buttons and the TMemo are required for the sample, the remaining is optional.

116 COMPONENTS
DEVELOPERS 4 Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE
Having fun with Delphi and AMQP (Continuation 5)
COMPONENTS
DEVELOPERS 4
The IP address can be changed to match the address In our case we simply update a counter, counting
of your RabbitMQ server (perhaps 127.0.0.1) if its how many messages we have received. We could
running on the same machine as your kbmMW also have extracted the received data (as the
AMQP client. commented code shows).
Next step is to declare at least one queue, which
Having a connection is great. will be used for temporary storage of data in the
However we also need to define a channel. AMQP server. The sample shows how to declare
What is a channel? multiple queues, and wait for acknowledgement
from the server that the queues has been formed. As
AMQP allows for multiple parallel streams of data
DeclareQueue is an async function, it will not wait
being passed via separate channels concurrently.
for the queue actually having been declared on the
At the same time the AMQP protocol guarantees that
server, however the kbmMW AMQP client does
messages pushed via a single channel is always sent
keep track of acknowledgements or failures from the
in the order that the data has been pushed. server when they turn up. By calling WaitAcks, the
You will normally want to have a channel per thread, client program stalls until all 3 queues have been
in a multithreaded environment, to ensure that your confirmed to be created on the server.
communication is running asynchronously. Even though a queue may already have been
So that's the reason for the Open channel button. declared and created by another AMQP client (or
Lets add some code for it. manually via the RabbitMQ management interface),
you still need to declare it in your own client. If the
private queue do not exist on the server, it will default be
FClient:TkbmMWAMQPClient; created. If it does exist, then it will be used as is, but
FChannel:IkbmMWAMQPChannel;
procedure only if you declare it with the exact same options (eg.
OnContent(Channel:IkbmMWAMQPChannel; mwaqoAutoDelete etc) as when the queue was
AContent:TkbmMWMemoryStream); declared first time. If it has been declared with
different options, then you will receive an error and
procedure TForm1.btnOpenChannelClick(Sender:
TObject);
be disconnected from the AMQP server.
begin In fact the AMQP protocol states that any error
FChannel:=FClient.OpenChannel; happening due to wrong/illegal request from an
FChannel.OnContent:=OnContent; AMQP client MUST result in disconnection from the
end;
server, immediately after the server have sent an
procedure TForm1.btnCloseChannelClick(Sender: asynchronous notice about the reason. It can be
TObject); handy to look in the RabbitMQ log directory to check
begin up on reasons for disconnections, if you do not
FClient.CloseChannel(FChannel);
end;
understand why you are being disconnected.

procedure private
TForm1.OnContent(Channel:IkbmMWAMQPChannel; { Private declarations }
AContent:TkbmMWMemoryStream); FClient:TkbmMWAMQPClient;
//var // s:string; FChannel:IkbmMWAMQPChannel;
begin FQueue1,
// The following will be executed in a thread, and should FQueue2,
// thus be synchronized to be threadsafe. FQueue3:IkbmMWAMQPQueue;
// However for simplicity of the sample procedure
OnContent(Channel:IkbmMWAMQPChannel;
//we don't do this now.
AContent:TkbmMWMemoryStream);
// However NEVER run code in production that updates

// the GUI from another thread without using
procedure TForm1.btnDeclareNamedQueueClick(
//Synchronize because it WILL fail! Sender: TObject);
// s := TkbmMWPlatformMarshal.UTF8Decode( begin
// AContent.Memory,AContent.Size); FQueue1:=FClient.DeclareQueue(
// mLog.Lines.Add( FChannel,'QUEUE1',[mwaqoAutoDelete]);
//'Got'+ IntToStr(AContent.Size) FQueue2:=FClient.DeclareQueue(
// +' Bytes of content: '+ s); FChannel,'QUEUE2',[mwaqoAutoDelete]);
FQueue3:=FClient.DeclareQueue(
TkbmMWInterlocked.Increment(FReceived); FChannel,'QUEUE3',[mwaqoAutoDelete]);
end; FChannel.WaitAcks;
end;

We will need to hold on to the returned channel as we


will need it. Further we have defined a callback
method which will be called whenever data is
received for the kbmMW AMQP client on the channel.
Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE
COMPONENTS
DEVELOPERS 4 117
Having fun with Delphi and AMQP (Continuation 6)

COMPONENTS
DEVELOPERS 4
Now 3 queues have been declared. However we can't private
directly push messages to a queue in AMQP. { Private declarations }
We instead communicate with a message exchange FClient:TkbmMWAMQPClient;
which acts like a post office, and decides which FChannel:IkbmMWAMQPChannel;
FQueue1,
queues are to receive which messages. There are a FQueue2,
number of different exchange types to choose FQueue3:IkbmMWAMQPQueue;
between - direct, fanout and topic - to name a couple. FExchange:IkbmMWAMQPExchange;
The direct exchange type is used for delivering a FConsumer1:IkbmMWAMQPConsumer;
procedure
message directly to a specifically named queue. OnContent(Channel:IkbmMWAMQPChannel;
The fanout exchange type is used for delivering a AContent:TkbmMWMemoryStream);
message to all queues directly bound to that
exchange, and the topic exchange type is used for procedure TForm1.btnDeclareConsumerClick(
Sender: TObject);
delivering a message to all queues that is bound via a
begin
subscription that match with the topic. FConsumer1:=FChannel.Consumers.DeclareConsumer(
This leads to exchange / queue binding. 'ACONSUMER',FQueue2);
After declaring an exchange (which will create it if it end;
does not already exist on the server, in the same way as Now all plumbing is done, and we can publish
with queues), queues needs to be bound to the our first message to the AMQP server.
exchange. So lets write some code to do that. procedure TForm1.btnPublish1Click(
Sender: TObject);
private begin
{ Private declarations } FChannel.Publisher.Publish(
FClient:TkbmMWAMQPClient; FExchange,'',[],'HELLO');
FChannel:IkbmMWAMQPChannel; end;
FQueue1,
AMQP basically only supports sending binary data
FQueue2,
FQueue3:IkbmMWAMQPQueue; as message data, but to simplify things, the kbmMW
FExchange:IkbmMWAMQPExchange; AMQP client provides a couple of overloaded
procedure Publish methods for sending a string, a memory
OnContent(Channel:IkbmMWAMQPChannel;
stream and an array of bytes.
AContent:TkbmMWMemoryStream);
The string will automatically be UTF8 encoded
procedure TForm1.btnDeclareExchangeClick( before being sent so the receiver should UTF8
Sender: TObject); decode it before use.
begin
When clicking the Publish 1 button, a message is
FExchange:=FClient.DeclareExchange(
FChannel,'MYEXCHANGE', published via the exchange we have declared.
KBMMW_AMQP_EXCHANGETYPE_FANOUT, The message will end up on the queue Queue2,
[mwaeoAutoDelete]); which we have declared a consumer for, why we will
FClient.BindQueue(FQueue2,FExchange,''); receive the message again in the OnContent event.
end;
It's allowed to declare a queue without a name.
In this code, we have declared a fanout type What happens is that the server will
exchange, and bound our Queue2 to it. So whenever automatically create a unique name and assign that
we post something to the exchange MYEXCHANGE, name to the queue.
the data will automatically be put into the queue kbmMW AMQP will get to know about the chosen
named Queue2. If other queues has been bound to name and will update the queue variable with the
the exchange, they will also receive a copy of the new name. If you want to ensure that the queue has
message. But in our case we also want to receive been given a name, before you start to do other
messages. To do that we need to define a consumer. operations (like binding it to an exchange), you should
A consumer is always bound to a specific queue. call the channel.WaitArgs method.
Its allowed to declare multiple consumers, one for After it returns you are guaranteed to have been
each queue we want to receive messages from. handed a unique name for the queue and thus is able
The consumer name is only used for if we want to to bind it to an exchange and/or a consumer.
explicitly delete the consumer again later. You can click the Show Declared queues after
However we don't need to do that, because the WaitArgs and see the server generated queue name.
moment we close the connection, the server will The TTimer we have on the form is only used for
automatically delete all consumers we have declared.
displaying the value of the received number of
Notice though that the queues are not deleted (unless
messages.
we have setup options to do so, when we declared the
queue), and thus will continue to receive messages,
which the client can consume next time it connects,
and declares a consumer for those queues.
118 COMPONENTS
DEVELOPERS 4 Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE
Having fun with Delphi and AMQP (Continuation 7-end)
COMPONENTS
DEVELOPERS 4
procedure TForm1.Timer1Timer(Sender: TObject); Now you are all set for integrating your favorite
var
i:integer;
Delphi/C++Builder/FreePascal based application
begin with any AMQP 0.91 server in the world.
i:=TkbmMWInterlocked.Exchange(FReceived,0);
mLog.Lines.Add(inttostr(i));
end;

The remaining optional buttons could


contain code like this:

procedure TForm1.btnDeleteConsumerClick( Sender: TObject);


begin
FChannel.Consumers.DeleteConsumer(
FConsumer1);
FConsumer1:=nil;
end;

procedure TForm1.btnDeclareNoNameQueueClick( Sender: TObject);


begin
FQueueNoName:=FClient.DeclareQueue(
FChannel,'',[mwaqoAutoDelete]);
end;

procedure TForm1.btnShowDeclaredQueuesClick(Sender: TObject);


var
i:integer;
q:IkbmMWAMQPQueue;
begin
for i:=0 to FClient.Queues.Count-1 do
begin
q:=FClient.Queues.Queue[i];
mLog.Lines.Add('Queue '+inttostr(i)+'='+q.QueueName);
end;
end;

COMPONENTS
DEVELOPERS 4 Specialist help and consultancy
for kbmMW

Benno Evers is our specialist for


questions about kbmMW.
He can help you with basic
questions regarding kbmMW as
well as with turnkey Development
and Consultancy. Hes a specialist
in netwoks, internet and CUSTOM TECHNOLOGY
hardware. b.evers@eversct.nl

Nr 5 / 6 2014 BLAISE PASCAL MAGAZINE


COMPONENTS
DEVELOPERS 4 119
IN A
NUTSHELL :
ONE + ONE = THREE
KBMMW V. 4.60 AMQP support
( Advanced Message Queuing Protocol)

- Added AMQP 0.91 client side gateway Supports Delphi/C++Builder/RAD Studio 2009
support and sample. to XE7 (32 bit, 64 bit and OSX where applicable).
- Updated StreamSec TLS transport plugin kbmMW for XE5 to XE7 includes full support for
component (by StreamSec). Android and IOS (client and server).!
- Improved performance on Indy TCP/IP
kbmMemTable is the fastest and most feature rich
Client messaging transport for large number in memory table for Embarcadero products.
of inbound messages.
- Easily supports large datasets
- Native high performance 100% developer with millions of records
defined application server with support for - Easy data streaming support
loadbalancing and failover - Optional to use native SQL engine
- Native high performance JSON and XML - Supports nested transactions and undo
- Native and fast build in M/D,
(DOM and SAX) for easy integration with
aggregation /grouping,
external systems range selection features
- Native support for RTTI assisted object - Advanced indexing features for
marshalling to and from XML/JSON, now also extreme performance
with new fullfeatured XML schema
(XSD) import Warning!
- High speed, unified database access kbmMemTable and kbmMW
(35+ supported database APIs) with
connection pooling, metadata and
are highly addictive!
Once used, and you are hooked for life!
data caching on all tiers
- Multi head access to the application server,
via AJAX, native binary, Publish/Subscribe,
SOAP, XML, RTMP from web browsers,
embedded devices, linked application
servers, PCs, mobile devices, Java systems
and many more clients
- Full FastCGI hosting support.
Host PHP/Ruby/Perl/Python applications in
kbmMW!
COMPONENTS
DEVELOPERS 4
EESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64,
.NET, LINUX, UNIX MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.

You might also like