Implementing a profile tutorial (C API)

Introduction

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.

Implementing the server.

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 */
"Received Ok: %s",
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",
/* return axl_true to accept the connection to be created */
return axl_true;
}
int main (int argc, char ** argv)
{
char * listening_on;
/* create the context */
ctx = vortex_ctx_new ();
/* init vortex library */
if (! vortex_init_ctx (ctx)) {
/* unable to init context */
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 */
listening_on = "0.0.0.0";
if (argc > 1)
listening_on = argv[1];
printf ("INFO: listening on %s\n", listening_on);
vortex_listener_new (ctx, listening_on, "44000", NULL, NULL);
/* configure connection notification */
/* wait for listeners (until vortex_exit is called) */
/* 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:

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.

Testing the server created using the vortex-client tool and telnet command.

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.

Implementing a client for our server

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;
char * host_to_connect;
/* init vortex library */
ctx = vortex_ctx_new ();
if (! vortex_init_ctx (ctx)) {
/* unable to init vortex */
return -1;
} /* end if */
/* creates a new connection against localhost:44000 */
host_to_connect = "localhost";
if (argc > 1)
host_to_connect = argv[1];
/* show what we are doing, a: hey, yes, this is important dude, b: important why? ... a: what kind of creature you are? */
printf ("INFO: connecting to %s:44000...\n", host_to_connect);
if (strstr (host_to_connect, ":")) {
printf ("INFO: using IPv6 api\n");
connection = vortex_connection_new6 (ctx, host_to_connect, "44000", NULL, NULL);
} else {
printf ("INFO: using IPv4 api\n");
connection = vortex_connection_new (ctx, host_to_connect, "44000", NULL, NULL);
}
if (!vortex_connection_is_ok (connection, axl_false)) {
printf ("Unable to connect remote server, error was: %s\n",
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 */
/* 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_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 has arrived: (size: %d):\n%s\n",
(char*) vortex_frame_get_payload (frame));
/* release frame */
end:
/* terminate execution context */
vortex_exit_ctx (ctx, axl_false);
/* 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.

Conclusion

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.