5.4. Calculator client & server

This example will take you further into understanding how to actually make a working server, using the support that comes from ORBit. It will demonstrate handling of replies from server.

The system will not be doing very much. The server just provides two functions, one to add two numbers and one to subtract two numbers. The first thing you have to do is to write the IDL files for the server. In our example it is very simple.

Example 5-9. calculator.idl


interface Calculator
{
      double add(in double number1, in double number2);
      double sub(in double number1, in double number2);
};

Then you have to generate the skeleton and stub files. In addition to these two files the ORBit IDL compiler also generates a common file and a header file. The common file implements the memory management functions and other things, useful in the client as well as in the server. The sequence to generate the C source files is rather simple. $ orbit-idl-2 --skeleton-impl calculator.idl geenrates all the files we will use in this example.

FileUsage for ClientUsage for Server
calculator.hreadonlyreadonly
calculator-common.creadonlyreadonly
calculator-stubs.creadonly-
calculator-skels.c-readonly
calculator-skelimpl.c-template for user code

Files remaining to write are listed in following table, starting with calculator-client.c in following chapter.

calculator-client.cwrite the client code
calculator-server.cwrite the generic code for servant creation

5.4.1. Calculator client

The next thing you have to do is to write the server and client programs. We start with the client, because it's easier and not very complicated.

A simple implementation of the client might look like this

Example 5-10. calculator-client.c

/* calculator-client.c hacked by Frank Rehberger
 * <F.Rehberger@xtradyne.de>.  */

#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <orbit/orbit.h>

#include "calculator.h"

#include "examples-toolkit.h"

static CORBA_ORB  global_orb = CORBA_OBJECT_NIL; /* global orb */
 
/* Is called in case of process signals. it invokes CORBA_ORB_shutdown()
 * function, which will terminate the processes main loop.
 */
static
void
client_shutdown (int sig)
{
        CORBA_Environment  local_ev[1];
        CORBA_exception_init(local_ev);
 
        if (global_orb != CORBA_OBJECT_NIL)
        {
                 CORBA_ORB_shutdown (global_orb, FALSE, local_ev); 
                etk_abort_if_exception (local_ev, "caught exception");
        }
}
 
        
/* Inits ORB @orb using @argv arguments for configuration. For each
 * ORBit options consumed from vector @argv the counter of @argc_ptr
 * will be decremented. Signal handler is set to call
 * echo_client_shutdown function in case of SIGINT and SIGTERM
 * signals.  If error occures @ev points to exception object on
 * return.
 */
static
void
client_init (int               *argc_ptr,
	     char              *argv[],
             CORBA_ORB         *orb,
             CORBA_Environment *ev)
{
        /* init signal handling */
 
        signal(SIGINT,  client_shutdown);
        signal(SIGTERM, client_shutdown);
         
        /* create Object Request Broker (ORB) */
         
         (*orb) = CORBA_ORB_init(argc_ptr, argv, "orbit-local-mt-orb", ev); 
        if (etk_raised_exception(ev)) return;
}

/* Releases @servant object and finally destroys @orb. If error
 * occures @ev points to exception object on return.
 */
static
void
client_cleanup (CORBA_ORB                 orb,
                CORBA_Object              service,
                CORBA_Environment        *ev)
{
        /* releasing managed object */
         CORBA_Object_release(service, ev); 
        if (etk_raised_exception(ev)) return;
 
        /* tear down the ORB */
        if (orb != CORBA_OBJECT_NIL)
        {
                /* going to destroy orb.. */
                 CORBA_ORB_destroy(orb, ev); 
                if (etk_raised_exception(ev)) return;
        }
}

/**
 *
 */
static
void
client_run (Calculator         service,
	    CORBA_Environment *ev)
{
	CORBA_double      result=0.0;

	/*
         * use calculator service
         */
	
         result = Calculator_add(service, 1.0, 2.0, ev); 
        if (etk_raised_exception (ev)) return;
	
        /* prints results to console */
        g_print("Result: 1.0 + 2.0 = %2.0f\n", result);
}

/*
 * main 
 */
int
main(int argc, char* argv[])
         
{
        CORBA_char  filename[] = "calculator.ref";

	Calculator service = CORBA_OBJECT_NIL;

        CORBA_Environment ev[1];
        CORBA_exception_init(ev);

	 client_init (&argc, argv, &global_orb, ev); 
	etk_abort_if_exception(ev, "init failed");

	g_print ("Reading service reference from file \"%s\"\n", filename);

	 service = (Calculator) etk_import_object_from_file (global_orb,
							       filename,
							       ev); 
        etk_abort_if_exception(ev, "import service failed");

	 client_run (service, ev); 
        etk_abort_if_exception(ev, "service not reachable");
 
	 client_cleanup (global_orb, service, ev); 
        etk_abort_if_exception(ev, "cleanup failed");
 
        exit (0);
}

Rather simple, but full of unexplained stuff. Let's take a close look to the defined variables.

env

This varaible is used to hold information about exceptions which might have occurred during a function call. How to use this variable to detect errors in function will be explained in a later example.

orb

This is the ORB itself.

server

This is the object reference to the server.

The example above is a full functional client. The magic in this example is the usage of the function CORBA_ORB_string_to_object with the parameter argv[1]. The explantion is that the program is supposed to be called with the string representation of the Calculator server as the first parameter. How to obtain this string, will be shown in the next example, where I describe how the server.

5.4.2. Calculator server

To implement the server, the IDL compiler does a great deal of work for you. It can emit all the stuff necessary to set up the data structures and function calls for the server implementation. All you have to write is the setup stuff in your main function and the actual implementation of the server functions. First I'll present the functions and data structures the IDL compiler generates and then I'll show what's necessary to set up the environment for these functions to work properly.

5.4.2.1. Calculator Implementation Skeleton

To ease the task of implementing the calculator the ORBit IDL compiler can output an implementation skeleton of the server. This is enabled with the --skeleton-impl switch to the IDL compiler. The output of orbit-idl-2 --skeleton-impl calculator.idl looks like this (the default name for the generated source file is calculator-skelimpl.c:

Example 5-11. calculator-skelimpl.c

#include "calculator.h"

/*** App-specific servant structures ***/

typedef struct
{
   POA_Calculator servant;
   PortableServer_POA poa;

   /* ------ add private attributes here ------ */
   /* ------ ---------- end ------------ ------ */
}
impl_POA_Calculator;

/*** Implementation stub prototypes ***/

static void impl_Calculator__fini(impl_POA_Calculator * servant,
				  CORBA_Environment * ev);
static CORBA_double
impl_Calculator_add(impl_POA_Calculator * servant,
		    const CORBA_double number1,
		    const CORBA_double number2, 
		    CORBA_Environment * ev);

static CORBA_double
impl_Calculator_sub(impl_POA_Calculator * servant,
		    const CORBA_double number1,
		    const CORBA_double number2, 
		    CORBA_Environment * ev);

/*** epv structures ***/

static PortableServer_ServantBase__epv impl_Calculator_base_epv = {
   NULL,			/* _private data */
   (gpointer) & impl_Calculator__fini,	/* finalize routine */
   NULL,			/* default_POA routine */
};
static POA_Calculator__epv impl_Calculator_epv = {
   NULL,			/* _private */
   (gpointer) & impl_Calculator_add,

   (gpointer) & impl_Calculator_sub,

};

/*** vepv structures ***/

static POA_Calculator__vepv impl_Calculator_vepv = {
   &impl_Calculator_base_epv,
   &impl_Calculator_epv,
};

/*** Stub implementations ***/

static Calculator
impl_Calculator__create(PortableServer_POA poa, 
                        CORBA_Environment * ev)
{
   Calculator retval;
   impl_POA_Calculator *newservant;
   PortableServer_ObjectId *objid;

   newservant = g_new0(impl_POA_Calculator, 1);
   newservant->servant.vepv = &impl_Calculator_vepv;
   newservant->poa = poa;
   POA_Calculator__init((PortableServer_Servant) newservant, ev);
   /* Before servant is going to be activated all
    * private attributes must be initialized.  */

   /* ------ init private attributes here ------ */
   /* ------ ---------- end ------------- ------ */

   objid = PortableServer_POA_activate_object(poa, newservant, ev);
   CORBA_free(objid);
   retval = PortableServer_POA_servant_to_reference(poa, newservant, ev);

   return retval;
}

static void
impl_Calculator__fini (impl_POA_Calculator * servant,
		       CORBA_Environment * ev)
{
   CORBA_Object_release((CORBA_Object) servant->poa, ev);
 
   /* No further remote method calls are delegated to 
    * servant and you may free your private attributes. */
   /* ------ free private attributes here ------ */
   /* ------ ---------- end ------------- ------ */

   POA_Calculator__fini((PortableServer_Servant) servant, ev);

   g_free (servant);
}

static CORBA_double
impl_Calculator_add(impl_POA_Calculator * servant,
		    const CORBA_double number1,
		    const CORBA_double number2, 
		    CORBA_Environment * ev)
{
   CORBA_double retval;

   /* ------   insert method code here   ------ */
   retval = number1 + number2;
   /* ------ ---------- end ------------ ------ */

   return retval;
}

static CORBA_double
impl_Calculator_sub(impl_POA_Calculator * servant,
                    const CORBA_double number1,
                    const CORBA_double number2, 
		    CORBA_Environment * ev)
{
   CORBA_double retval;

   /* ------   insert method code here   ------ */
   retval = number1 - number2;
   /* ------ ---------- end ------------ ------ */

   return retval;
}

This source file provides you with most of the magic of a server. Note that we generate this file (with the --skeleton-impl switch) only once, and then the makefile invokes orbit-idl-2 with no switch. If you call orbit-idl-2 --skeleton-impl from the makefile, the previous file will be overwritten and your implementation code lost. Once the implementation code is written, just include the source file at the beginning of the calculator-server.c file.

For this first example, I won't explain all the bits and pieces of the generated source file. This will be done later. We'll just concentrate on getting the server running.

As you see there are two functions:

CORBA_double impl_Calculator_add(impl_POA_Calculator* servant, CORBA_double number1, CORBA_double number2, CORBA_Environment* ev);

and

CORBA_double impl_Calculator_sub(impl_POA_Calculator* servant, CORBA_double number1, CORBA_double number2, CORBA_Environment* ev);

These two functions are implementing the function defined in the IDL file. Because the IDL compiler doesn't provide you with a real implementation (it doesn't know what the function should do), you have to extend this skeleton yourself where marked.

The impl_Calculator_add() should add it's two parameters and return the result so this function should be changed into:

Example 5-12. calculator-skelimpl.c fragment


static CORBA_double
impl_Calculator_add(impl_POA_Calculator * servant,
                    const CORBA_double number1,
                    const CORBA_double number2, 
		    CORBA_Environment * ev)
{
   CORBA_double retval;

   /* ------   insert method code here   ------ */
   retval = number1 + number2;
   /* ------ ---------- end ------------ ------ */

   return retval;
}

5.4.2.2. Calculator Server Implementation

The things you need in your minimal main function to make things work can be implemented in the following way, note analogy to echo-server.c of previous example.

Example 5-13. calculator-server.c

/*
 * calculator-server program. Hacked from Frank Rehberger
 * <F.Rehberger@xtradyne.de>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <orbit/orbit.h>

#include "calculator.h"
#include "calculator-skelimpl.c"

#include "examples-toolkit.h"

static CORBA_ORB          global_orb = CORBA_OBJECT_NIL; /* global orb */
static PortableServer_POA root_poa   = CORBA_OBJECT_NIL; /* root POA
	
/* Is called in case of process signals. it invokes CORBA_ORB_shutdown()
 * function, which will terminate the processes main loop.
 */
static
void
server_shutdown (int sig)
{
	CORBA_Environment  local_ev[1];
	CORBA_exception_init(local_ev);

        if (global_orb != CORBA_OBJECT_NIL)
        {
                 CORBA_ORB_shutdown (global_orb, FALSE, local_ev); 
                etk_abort_if_exception (local_ev, "caught exception");
        }
}

/* Inits ORB @orb using @argv arguments for configuration. For each
 * ORBit options consumed from vector @argv the counter of @argc_ptr
 * will be decremented. Signal handler is set to call
 * echo_server_shutdown function in case of SIGINT and SIGTERM
 * signals.  If error occures @ev points to exception object on
 * return.
 */static 
void 
server_init (int                 *argc_ptr, 
	     char                *argv[],
	     CORBA_ORB           *orb,
	     PortableServer_POA  *poa,
	     CORBA_Environment   *ev)
{
	PortableServer_POAManager  poa_manager = CORBA_OBJECT_NIL; 

	CORBA_Environment  local_ev[1];
	CORBA_exception_init(local_ev);

	/* init signal handling */
	signal(SIGINT,  server_shutdown);
	signal(SIGTERM, server_shutdown);
	
	/* create Object Request Broker (ORB) */
	
         (*orb) = CORBA_ORB_init(argc_ptr, argv, "orbit-local-mt-orb", ev); 
	if (etk_raised_exception(ev)) 
		goto failed_orb;

         (*poa) = (PortableServer_POA) 
		CORBA_ORB_resolve_initial_references(*orb, "RootPOA", ev); 
	if (etk_raised_exception(ev)) 
		goto failed_poa;

         poa_manager = PortableServer_POA__get_the_POAManager(*poa, ev); 
	if (etk_raised_exception(ev)) 
		goto failed_poamanager;

	 PortableServer_POAManager_activate(poa_manager, ev); 
	if (etk_raised_exception(ev)) 
		goto failed_activation;

         CORBA_Object_release ((CORBA_Object) poa_manager, ev); 
	return;

 failed_activation:
 failed_poamanager:
        CORBA_Object_release ((CORBA_Object) poa_manager, local_ev);
 failed_poa:
	CORBA_ORB_destroy(*orb, local_ev);		
 failed_orb:
	return;
}

/* Entering main loop @orb handles incoming request and delegates to
 * servants. If error occures @ev points to exception object on
 * return.
 */
static void 
server_run (CORBA_ORB          orb,
	    CORBA_Environment *ev)
{
        /* enter main loop until SIGINT or SIGTERM */
	
         CORBA_ORB_run(orb, ev); 
	if (etk_raised_exception(ev)) return;

        /* user pressed SIGINT or SIGTERM and in signal handler
	 * CORBA_ORB_shutdown(.) has been called */
}

/* Releases @servant object and finally destroys @orb. If error
 * occures @ev points to exception object on return.
 */
static void 
server_cleanup (CORBA_ORB           orb,
		PortableServer_POA  poa,
		CORBA_Object        ref,
		CORBA_Environment  *ev)
{
	PortableServer_ObjectId   *objid       = NULL;

	 objid = PortableServer_POA_reference_to_id (poa, ref, ev); 
	if (etk_raised_exception(ev)) return;
		
	/* Servant: deactivatoin - will invoke  __fini destructor */
	 PortableServer_POA_deactivate_object (poa, objid, ev); 
	if (etk_raised_exception(ev)) return;

	 PortableServer_POA_destroy (poa, TRUE, FALSE, ev); 
	if (etk_raised_exception(ev)) return;

	CORBA_free (objid);

         CORBA_Object_release ((CORBA_Object) poa, ev); 
	if (etk_raised_exception(ev)) return;
	
         CORBA_Object_release (ref, ev); 
	if (etk_raised_exception(ev)) return;

        /* ORB: tear down the ORB */
        if (orb != CORBA_OBJECT_NIL)
        {
                /* going to destroy orb.. */
                 CORBA_ORB_destroy(orb, ev); 
		if (etk_raised_exception(ev)) return;
        }
}

/* Creates servant and registers in context of ORB @orb. The ORB will
 * delegate incoming requests to specific servant object.  @return
 * object reference. If error occures @ev points to exception object
 * on return.
 */
static CORBA_Object
server_activate_service (CORBA_ORB           orb,
			 PortableServer_POA  poa,
			 CORBA_Environment  *ev)
{
	Calculator  ref = CORBA_OBJECT_NIL; 

	 ref = impl_Calculator__create (poa, ev); 
	if (etk_raised_exception(ev)) 
		return CORBA_OBJECT_NIL;
	
	return ref;
}

/* 
 * main 
 */

int
main (int argc, char *argv[])
{
	CORBA_Object servant = CORBA_OBJECT_NIL;
	
	CORBA_char filename[] = "calculator.ref";

	CORBA_Environment  ev[1];
	CORBA_exception_init(ev);
	
	 server_init (&argc, argv, &global_orb, &root_poa, ev); 
	etk_abort_if_exception(ev, "failed ORB init");

	 servant = server_activate_service (global_orb, root_poa, ev); 
	etk_abort_if_exception(ev, "failed activating service");

	g_print ("Writing service reference to: %s\n\n", filename);

	 etk_export_object_to_file (global_orb, 
				   servant, 
				   filename, 
				   ev); 
	etk_abort_if_exception(ev, "failed exporting IOR");
	
	 server_run (global_orb, ev); 
	etk_abort_if_exception(ev, "failed entering main loop");

	 server_cleanup (global_orb, root_poa, servant, ev); 
	etk_abort_if_exception(ev, "failed cleanup");

	exit (0); }

I'm not going to explain every line of this example yet; you only need to know that it creates a servant object whos object-reference is written to file "calculator.ref". File-IO is done using specific function from "example-toolkit" laying beside each example; the file contains a string always starting with the magic sequence "IOR:" and is read by client application to get hold of servants object-reference. The IOR string contains sufficient information to get to know host, port and object key within server. ORBit2 provides tools to decode the IOR string, namely ior-decode-2.

5.4.3. Compiling server & client

The following makefile can be used to compile both, the client and the server. Be aware of the location of ORBit : on my system it has been installed under /usr but it could be /usr/local if you have built it from the sources, and hence the path for ORBIT variables below may vary. If using ORBit binary packages shipped with Linux or BSD/Unix the simple makefile below will do.

Example 5-14. makefile

PREFIX ?= /usr
CC = gcc
TARGETS=calculator-client calculator-server
ORBIT_IDL=orbit-idl-2
CFLAGS=-DORBIT2=1 -D_REENTRANT -I$(PREFIX)/include/orbit-2.0 \
       -I$(PREFIX)/include/linc-1.0 -I$(PREFIX)/include/glib-2.0 \
       -I$(PREFIX)/lib/glib-2.0/include
LDFLAGS= -Wl,--export-dynamic -L$(PREFIX)/lib -lORBit-2 -llinc -lgmodule-2.0 \
             -ldl -lgobject-2.0 -lgthread-2.0 -lpthread -lglib-2.0 -lm
IDLOUT=calculator-common.c calculator-stubs.c calculator-skels.c calculator.h
 
all: $(IDLOUT) calculator-client calculator-server
 
calculator-server.o : calculator-server.c calculator-skelimpl.c

calculator-client : calculator-client.o calculator-common.o calculator-stubs.o
calculator-server : calculator-server.o calculator-common.o calculator-skels.o
 
$(IDLOUT): calculator.idl
        $(ORBIT_IDL) calculator.idl
 
clean:
        rm -rf *.o *~ $(IDLOUT)
 
distclean: clean
        rm -rf calculator-client calculator-server

After calling make in terminal window all sources have been compiled and you should open a second terminal window. In the first window we will start the server with the command: calculator-server > calculator.ref. The server should print a very long string into the file calculator.ref, starting with the 4 character sequence IOR: In the second window we start the client with the command calculator-client `cat calculator.ref` IOR-string. You should not try to type the IOR string, instead use the cut and paste functionality of your xterm or whatever you are using.

If everything works, you should get the following output: Result: 1.0 + 2.0 = 3.