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.
#include <vortex.h> #define PLAIN_PROFILE "http://fact.aspl.es/profiles/plain_profile" 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) { /* init vortex library */ vortex_init (); /* register a profile */ vortex_profiles_register (PLAIN_PROFILE, start_channel, NULL, close_channel, NULL, frame_received, NULL); /* create a vortex server */ vortex_listener_new ("0.0.0.0", "44000", NULL, NULL); /* configure connection notification */ vortex_listener_set_on_connection_accepted (on_accepted, NULL); /* wait for listeners (until vortex_exit is called) */ vortex_listener_wait (); /* end vortex function */ vortex_exit (); return 0; }
As you can see, the server code is fairly easy to understand. The following steps are done:
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.
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.
(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.
(jobs:0)[acinom@barbol libvortex] $ vortex-client Vortex-client v.0.8.3.b1498.g1498: a cmd tool to test vortex (and BEEP-enabled) peers Copyright (c) 2005 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 especial operations such as create new channels, close them and channel tuning.
[===] 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
[===] 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
[===] 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
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; int msg_no; /* init vortex library */ vortex_init (); /* creates a new connection against localhost:44000 */ printf ("connecting to localhost:44000...\n"); connection = vortex_connection_new ("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); vortex_exit (); 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 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 especial 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.
NOTE: All code developed on this tutorial can be found inside the Vortex Library repository, directory test.