Implementing a profile tutorial

Introduction

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"

void frame_received (VortexChannel    * channel,
                     VortexConnection * connection,
                     VortexFrame      * frame,
                     gpointer           user_data)
{
        g_print ("A frame received on channl: %d\n",     vortex_channel_get_number (channel));
        g_print ("Data received: '%s'\n",                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));
                                
        g_print ("VORTEX_LISTENER: end task (pid: %d)\n", getpid ());


        return;
}

gboolean start_channel (gint               channel_num, 
                        VortexConnection * connection, 
                        gpointer           user_data)
{
        // implement profile requirement for allowing starting a new channel

        // to return FALSE denies channel creation
        // to return TRUE allows create the channel
        return TRUE;
}

gboolean close_channel (gint               channel_num, 
                        VortexConnection * connection, 
                        gpointer           user_data)
{
        // implement profile requirement for allowing to closeing a the channel
        // to return FALSE denies channel closing
        // to return TRUE allows to close the channel
        return TRUE;
}

gint main (gint argc, gchar ** 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);

        // 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:

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"

gint main (gint argc, gchar ** argv)
{
        VortexConnection * connection;
        VortexChannel    * channel;
        VortexFrame      * frame;
        WaitReplyData    * wait_reply;
        gint               msg_no;

        // init vortex library
        vortex_init ();


        // creates a new connection against localhost:44000
        g_print ("connecting to localhost:44000...");
        connection = vortex_connection_new ("localhost", "44000", NULL, NULL);
        if (!vortex_connection_is_ok (connection, FALSE)) {
                g_print ("Unable to connect remote server, error was: %s",
                         vortex_connection_get_message (connection));
                goto end;
        }
        g_print ("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) {
                g_print ("Unable to create the channel..");
                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)) {
                g_print ("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) {
                g_print ("there was an error while receiving the reply or a timeout have occur\n");
                vortex_channel_close (channel, NULL);
                goto end;
        }
        g_print ("my reply have arrived: (size: %d):\n%s", 
                 vortex_frame_get_payload_size (frame), 
                 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 have 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 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.