The server

Let's begin with the server:

/*
 * ResMgr and Message Server Process
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>

resmgr_connect_funcs_t  ConnectFuncs;
resmgr_io_funcs_t       IoFuncs;
iofunc_attr_t           IoFuncAttr;


typedef struct
{
    uint16_t msg_no;
    char     msg_data[255];
} server_msg_t;


int message_callback( message_context_t *ctp, int type, unsigned flags, 
                      void *handle )
{
    server_msg_t *msg;
    int num;
    char msg_reply[255];

    /* Cast a pointer to the message data */
    msg = (server_msg_t *)ctp->msg;

    /* Print some useful information about the message */
    printf( "\n\nServer Got Message:\n" );
    printf( "  type: %d\n" , type );
    printf( "  data: %s\n\n", msg->msg_data );

    /* Build the reply message */
    num = type - _IO_MAX;
    snprintf( msg_reply, 254, "Server Got Message Code: _IO_MAX + %d", num );
   
    /* Send a reply to the waiting (blocked) client */ 
    MsgReply( ctp->rcvid, EOK, msg_reply, strlen( msg_reply ) + 1 );

    return 0;
}



int main( int argc, char **argv )
{
    resmgr_attr_t        resmgr_attr;
    message_attr_t       message_attr;
    dispatch_t           *dpp;
    dispatch_context_t   *ctp, *ctp_ret;
    int                  resmgr_id, message_id;

    /* Create the dispatch interface */
    dpp = dispatch_create();
    if( dpp == NULL )
    {
        fprintf( stderr, "dispatch_create() failed: %s\n", 
                 strerror( errno ) );
        return EXIT_FAILURE;
    }

    memset( &resmgr_attr, 0, sizeof( resmgr_attr ) );
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    /* Setup the default I/O functions to handle open/read/write/... */
    iofunc_func_init( _RESMGR_CONNECT_NFUNCS, &ConnectFuncs,
                      _RESMGR_IO_NFUNCS, &IoFuncs );

    /* Setup the attribute for the entry in the filesystem */
    iofunc_attr_init( &IoFuncAttr, S_IFNAM | 0666, 0, 0 );

    resmgr_id = resmgr_attach( dpp, &resmgr_attr, "serv", _FTYPE_ANY, 
                               0, &ConnectFuncs, &IoFuncs, &IoFuncAttr );
    if( resmgr_id == -1 )
    {
        fprintf( stderr, "resmgr_attach() failed: %s\n", strerror( errno ) );
        return EXIT_FAILURE;
    }

    /* Setup our message callback */
    memset( &message_attr, 0, sizeof( message_attr ) );
    message_attr.nparts_max = 1;
    message_attr.msg_max_size = 4096;

    /* Attach a callback (handler) for two message types */
    message_id = message_attach( dpp, &message_attr, _IO_MAX + 1,
                                 _IO_MAX + 2, message_callback, NULL );
    if( message_id == -1 )
    {
        fprintf( stderr, "message_attach() failed: %s\n", strerror( errno ) );
        return EXIT_FAILURE;
    }

    /* Setup a context for the dispatch layer to use */
    ctp = dispatch_context_alloc( dpp );
    if( ctp == NULL )
    {
        fprintf( stderr, "dispatch_context_alloc() failed: %s\n", 
                 strerror( errno ) );
        return EXIT_FAILURE;
    }


    /* The "Data Pump" - get and process messages */
    while( 1 )
    {
        ctp_ret = dispatch_block( ctp );
        if( ctp_ret )
        {
            dispatch_handler( ctp );
        }
        else
        {
            fprintf( stderr, "dispatch_block() failed: %s\n", 
                     strerror( errno ) );
            return EXIT_FAILURE;
        }
    }

    return EXIT_SUCCESS;
}

The first thing the server does is create a dispatch handle (dpp) using dispatch_create(). This handle will be used later when making other calls into the dispatch portion of the library. The dispatch layer takes care of receiving incoming messages and routing them to the appropriate layer (resmgr, message, pulse).

After the dispatch handle is created, the server sets up the variables needed to make a call into resmgr_attach(). But since we're not using the resmgr functionality for anything more than getting a connection ID to use with MsgSend(), the server sets up everything to the defaults.

We don't need (or want) to worry about I/O and connection messages right now (like the messages that open(), close(), read(), write() and so on generate); we just want them to work and do the right thing. Luckily, there are defaults built into the C library to handle these types of messages for you, and iofunc_func_init() sets up these defaults. The call to iofunc_attr_init() sets up the attribute structure so that the entry in the filesystem has the specified attributes.

Finally, the call to resmgr_attach() is made. For our purposes, the most important parameter is the third. In this case we're registering the filesystem entry serv. Since an absolute path wasn't given, the entry will appear in the same directory where the server was run. All of this gives us a filesystem entry that can be opened and closed, but generally behaves the same as /dev/null. But that's fine, since we want to be able to MsgSend() data to our server, not write() data to it.

Now that the resmgr portion of the setup is complete, we need to tell the dispatch layer that we'll be handling our own messages in addition to the standard I/O and connection messages handled by the resmgr layer. In order to let the dispatch layer know the general attributes of the messages we'll be receiving, we fill in the message_attr structure. In this case we're telling it that the number of message parts we're going to receive is 1 with a maximum message size of 4096 bytes.

Once we have these attributes defined, we can register our intent to handle messages with the dispatch layer by invoking message_attach(). With this call, we're setting up our message_callback() routine to be the handler of messages of type _IO_MAX + 1 up to and including messages of type _IO_MAX + 2. There's even the option of having a pointer to arbitrary data passed into the callback, but we don't need that so we're setting it to NULL.

You might now be asking, “Message type _IO_MAX + 1! I don't see anything in the MsgSend() docs for setting a message type!” This is true. However, in order to play nicely with the dispatch layer, all incoming messages must have a 32-bit integer at the start indicating the message type. Although this may seem restrictive to a new QNX Neutrino developer, the reason it's in place is that most designs will end up using some sort of message identification anyway, and this just forces you into a particular style. This will become clearer when we look at the client. But now let's finish the server.

Now that we've registered both the resmgr and message handlers with the dispatch layer, we simply create a context for the dispatch layer to use while processing messages by calling dispatch_context_alloc(), and then start receiving and processing data. This is a two-step process:

  1. The server calls dispatch_block(), which waits for incoming messages and pulses.
  2. Once there's data available, we call into dispatch_handler() to do the right thing based on the message data. It's inside the dispatch_handler() call that our message_callback() routing will be invoked, when messages of the proper type are received.

Finally, let's look at what our message_callback() routine actually does when a proper message is received. When a message of type _IO_MAX + 1 or _IO_MAX + 2 is received, our callback is invoked. We get the message type passed in via the type parameter. The actual message data can be found in ctp->msg. When the message comes in, the server prints the message type and the string that was sent from the client. It then prints the offset from _IO_MAX of the message type, and then finally formats a reply string and sends the reply back to the client via ctp->rcvid using MsgReply().