NOTE: All code developed on this tutorial can be found inside the Vortex Library repository, inside the test/ directory. Files created in this tutorial: vortex-simple-listener.c and vortex-simple-client.c. The following are the subversion links:
We are going to create a simple server which implements a simple profile defined as: every message it receives, is replied with the payload received appending "Received OK: ".
Additionally, we are going to create a simple client which tries to connect to the server and then send a message to him printing to the console the server's reply.
The profile implemented is called "http://fact.aspl.es/profiles/plain_profile". While implementing a profile you must chose a name based on a uri style. You can't chose already defined profiles name so it is a good idea to chose your profile name appending as prefix your project name. An example of this can be: "http://your.project.org/profiles/profile_name".
See how the plain_profile is implemented to get an idea on how more complex, and useful profiles could be implemented.
First, we have to create the source code for the server:
#include <vortex.h> #define PLAIN_PROFILE "http://fact.aspl.es/profiles/plain_profile" /* listener context */ VortexCtx * ctx = NULL; void frame_received (VortexChannel * channel, VortexConnection * connection, VortexFrame * frame, axlPointer user_data) { printf ("A frame received on channl: %d\n", vortex_channel_get_number (channel)); printf ("Data received: '%s'\n", (char*) vortex_frame_get_payload (frame)); /* reply the peer client with the same content */ vortex_channel_send_rpyv (channel, vortex_frame_get_msgno (frame), "Received Ok: %s", vortex_frame_get_payload (frame)); printf ("VORTEX_LISTENER: end task (pid: %d)\n", getpid ()); return; } int start_channel (int channel_num, VortexConnection * connection, axlPointer user_data) { /* implement profile requirement for allowing starting a new * channel */ /* to return axl_false denies channel creation to return axl_true * allows create the channel */ return axl_true; } int close_channel (int channel_num, VortexConnection * connection, axlPointer user_data) { /* implement profile requirement for allowing to closeing a * the channel to return axl_false denies channel closing to * return axl_true allows to close the channel */ return axl_true; } int on_accepted (VortexConnection * connection, axlPointer data) { printf ("New connection accepted from: %s:%s\n", vortex_connection_get_host (connection), vortex_connection_get_port (connection)); /* return axl_true to accept the connection to be created */ return axl_true; } int main (int argc, char ** argv) { /* create the context */ ctx = vortex_ctx_new (); /* init vortex library */ if (! vortex_init_ctx (ctx)) { /* unable to init context */ vortex_ctx_free (ctx); return -1; } /* end if */ /* register a profile */ vortex_profiles_register (ctx, PLAIN_PROFILE, start_channel, NULL, close_channel, NULL, frame_received, NULL); /* create a vortex server */ vortex_listener_new (ctx, "0.0.0.0", "44000", NULL, NULL); /* configure connection notification */ vortex_listener_set_on_connection_accepted (ctx, on_accepted, NULL); /* wait for listeners (until vortex_exit is called) */ vortex_listener_wait (ctx); /* end vortex function */ vortex_exit_ctx (ctx, axl_true); return 0; }
As you can see, the server code is fairly easy to understand. The following steps are done:
First of all, Vortex Library is initialized using vortex_init_ctx.
Then, PLAIN_PROFILE is registered inside Vortex Library using vortex_profiles_register. This means, the vortex listener we are building will recognize peer wanting to create new channels based on PLAIN_PROFILE.
NOTE: on connection startup every BEEP listener must report what profiles support. This allows BEEP initiators to figure out if the profile requested will be supported. Inside the Vortex Library implementation the registered profiles using vortex_profiles_register will be used to create the supported profiles list sent to BEEP initiators.
The other interesting question the BEEP Core definition have is that BEEP initiators, the one which is actually connecting to a listener doesn't need to report its supported profiles. As a consequence, you can create a Vortex Client connecting to a remote server without registering a profile.
Obviously, this doesn't means you are not required to implement the profile. A profile is always needed by definition by both peers to know the semantic under which the messages exchange will take place.
While registering a profile, Vortex Library will allow you to register several call backs to be called on event such us channel start, channel close and frame receive during the channel's life. This event will be called (actually registered handlers) only for those channels working under the semantic of PLAIN PROFILE.
As a conclusion, you can have several profiles implemented, having several channels opened on the same connection "running" different profiles at the same time.
Additionally, you may require to know current connection role. This is done by using vortex_connection_get_role function.
Next, a call to vortex_listener_new creates a server listener prepared to receive connection to any name the host may have listening on port 44000. In fact, you can actually perform several calls to vortex_listener_new to listen several port at the same time.
Before previous call, it is needed to call vortex_listener_wait to block the main thread until Vortex Library is finished.
That's all, you have created a Vortex Server supporting a user defined profile which replies to all message received appending "Received OK: " to them. To compile it you can check out this section.
Now, you can run the server and test it using a telnet tool to check some interesting things. The output you should get should be somewhat similar to the following:
(jobs:1)[acinom@barbol test] $ ./vortex-simple-listener & [2] 7397 (jobs:2)[acinom@barbol test] $ telnet localhost 44000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. RPY 0 0 . 0 128 Content-Type: application/BEEP+xml <greeting> <profile uri='http://fact.aspl.es/profiles/plain_profile' /> </greeting> END
As you can see, the server replies immediately its availability reporting the profiles it actually support. Inside the greeting element we can observe the server support the PLAIN_PROFILE.
Before starting to implement the vortex client, we can use vortex-client tool to check our new server. Launch the vortex-client tool and perform the following operations.
First, connect to the server located at localhost, port 44000 using vortex-client tool and once connected, show supported profiles by the remote host.
(jobs:0)[acinom@barbol libvortex-1.1] $ vortex-client Vortex-client v.0.8.3.b1498.g1498: a cmd tool to test vortex (and BEEP-enabled) peers Copyright (c) 2010 Advanced Software Production Line, S.L. [=|=] vortex-client > connect server to connect to: localhost port to connect to: 44000 connecting to localhost:44000..ok: vortex message: session established and ready [===] vortex-client > connection status Created channel over this session: channel: 0, profile: not applicable [===] vortex-client > show profiles Supported remote peer profiles: http://fact.aspl.es/profiles/plain_profile
As you can observe, vortex-client tool is showing we are already connected to remote peer and the connection created already have a channel created with number 0. This channel number is the BEEP administrative channel and every connection have it. This channel is used to perform special operations such as create new channels, close them and channel tuning.
Now, create a new channel choosing the plain profile as follows:
[===] vortex-client > new channel This procedure will request to create a new channel using Vortex API. Select what profile to use to create for the new channel? profiles for this peer: 1 1) http://fact.aspl.es/profiles/plain_profile 0) cancel process you chose: 1 What channel number to create: you chose: 4 creating new channel..ok, channel created: 4
Now, send a test message and check if the server reply is following the implementation, that is, the message should have "Received OK: " preceding the text sent. Notify to vortex-client you want to wait for the reply.
[===] vortex-client > send message This procedure will send a message using the vortex API. What channel do you want to use to send the message? you chose: 4 Type the message to send: This is a test, reply my message Do you want to wait for message reply? (Y/n) y Message number 0 was sent.. waiting for reply...reply received: Received Ok: This is a test, reply my message
Now, check connection status and channel status to get more data about them. This will be useful for you in the future is you want to debug BEEP peers.
[===] vortex-client > connection status Created channel over this session: channel: 0, profile: not applicable channel: 4, profile: http://fact.aspl.es/profiles/plain_profile [===] vortex-client > channel status Channel number to get status: 4 Channel 4 status is: Profile definition: http://fact.aspl.es/profiles/plain_profile Synchronization: Last msqno sent: 0 Next msqno to use: 1 Last msgno received: no message received yet Next reply to sent: 0 Next reply exp. to recv: 1 Next exp. msgno to recv: 0 Next seqno to sent: 32 Next seqno to receive: 45 [===] vortex-client > channel status Channel number to get status: 0 Channel 0 status is: Profile definition: not applicable Synchronization: Last msqno sent: 0 Next msqno to use: 1 Last msgno received: no message received yet Next reply to sent: 0 Next reply exp. to recv: 1 Next exp. msgno to recv: 0 Next seqno to sent: 185 Next seqno to receive: 228
[===] vortex-client > close channel
closing the channel..
This procedure will close a channel using the vortex API.
What channel number to close: you chose: 4
Channel close: ok
[===] vortex-client > close
[=|=] vortex-client > quit
Now we have implemented our server supporting the PLAIN PROFILE, we need some code to enable us sending data.
The following is the client implementation which connects, creates a new channel and send a message:
#include <vortex.h> #define PLAIN_PROFILE "http://fact.aspl.es/profiles/plain_profile" int main (int argc, char ** argv) { VortexConnection * connection; VortexChannel * channel; VortexFrame * frame; WaitReplyData * wait_reply; VortexCtx * ctx; int msg_no; /* init vortex library */ ctx = vortex_ctx_new (); if (! vortex_init_ctx (ctx)) { /* unable to init vortex */ vortex_ctx_free (ctx); return -1; } /* end if */ /* creates a new connection against localhost:44000 */ printf ("connecting to localhost:44000...\n"); connection = vortex_connection_new (ctx, "localhost", "44000", NULL, NULL); if (!vortex_connection_is_ok (connection, axl_false)) { printf ("Unable to connect remote server, error was: %s\n", vortex_connection_get_message (connection)); goto end; } printf ("ok\n"); /* create a new channel (by chosing 0 as channel number the * Vortex Library will automatically assign the new channel * number free. */ channel = vortex_channel_new (connection, 0, PLAIN_PROFILE, /* no close handling */ NULL, NULL, /* no frame receive async * handling */ NULL, NULL, /* no async channel creation */ NULL, NULL); if (channel == NULL) { printf ("Unable to create the channel..\n"); goto end; } /* create a wait reply */ wait_reply = vortex_channel_create_wait_reply (); /* now send the message using msg_and_wait/v */ if (!vortex_channel_send_msg_and_wait (channel, "my message", strlen ("my message"), &msg_no, wait_reply)) { printf ("Unable to send my message\n"); vortex_channel_free_wait_reply (wait_reply); vortex_channel_close (channel, NULL); goto end; } /* get blocked until the reply arrives, the wait_reply object * must not be freed after this function because it already * free it. */ frame = vortex_channel_wait_reply (channel, msg_no, wait_reply); if (frame == NULL) { printf ("there was an error while receiving the reply or a timeout have occur\n"); vortex_channel_close (channel, NULL); goto end; } printf ("my reply have arrived: (size: %d):\n%s\n", vortex_frame_get_payload_size (frame), (char*) vortex_frame_get_payload (frame)); end: vortex_connection_close (connection); /* terminate execution context */ vortex_exit_ctx (ctx, axl_false); /* free ctx */ vortex_ctx_free (ctx); return 0 ; }
As you can observe the client is somewhat more complicated than the server because it has to create not only the connection but also the channel, sending the message and use the wait reply method to read remote server reply.
Due to the test nature, we have used wait reply method so the test code gets linear in the sense "I send the message and I get blocked until the reply is received" but this is not the preferred method.
The Vortex Library preferred method is to install a frame receive handler and receive data replies or new message in an asynchronous way. But, doing this on this example maybe will produce to increase the complexity. If you want to know more about receiving data using other methods, check this section to know more about how can data is received.
We have seen how to create not only a profile but also a simple Vortex Server and a Vortex Client.
We have also seen how we can use vortex-client tool to test and perform operations against BEEP enabled peers in general and against Vortex Library peers in particular.
We have also talked about the administrative channel: the channel 0. This channel is present on every connection established and it is used for special operations about channel management.
In fact, the channel 0 is running under the definition of a profile defined at the RFC 3080 called Channel Management Profile. This is another example on how profiles are implemented: they only are definitions that must be implemented in order BEEP peers could notify others that they actually support it. In this case, *the Channel Profile Management is mandatory.
As another example for the previous point is the Coyote Layer inside the Af-Arch project. Coyote layer implements the profile:
http://fact.aspl.es/profiles/coyote_profile
On Af-Arch project, remote procedure invocation is done through a XML-RPC like message exchange defined and implemented at the Coyote layer (which is not XML-RPC defined at RFC3529).
If upper levels want to send a message to a remote Af-Arch enabled node, they do it through the Coyote layer. Coyote layer takes the message and transform it into a coyote_profile compliant message so remote peer, running also a Coyote layer, can recognize it.
In other words, the profile is registered using vortex_profiles_register and implemented on top of the Vortex Library.