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

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 10. calculator-client.c

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

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

#include "calculator.h"

/** 
 * test for exception 
 */
static
gboolean 
raised_exception(CORBA_Environment *ev) 
{
	return ((ev)->_major != CORBA_NO_EXCEPTION);
}

/**
 * in case of any exception this macro will abort the process  
 */
static
void 
abort_if_exception(CORBA_Environment *ev, const char* mesg) 
{
	if (raised_exception (ev)) {
		g_error ("%s %s", mesg, CORBA_exception_id (ev));
		CORBA_exception_free (ev); 
		abort(); 
	}
}

/*
 * main 
 */
int
main(int argc, char* argv[])
{
	char*             ior;

        CORBA_ORB         orb;
        CORBA_Object      server;

        CORBA_double      result=0.0;
	
        CORBA_Environment ev[1];
        CORBA_exception_init(ev);

	/* init - ORB might 'eat' arguments from command line */
        orb = CORBA_ORB_init(&argc, argv, "orbit-local-orb", ev);
	abort_if_exception(ev, "init ORB failed");

	/* make sure servant's IOR is given as command argument */
	if (argc<2)
		g_error ("usage: %s <ior>", argv[0]);
	ior=argv[1];
	
	/* establish servant connection */
        server = CORBA_ORB_string_to_object(orb, ior, ev);
	abort_if_exception(ev, "bind failed");

	/* 
	 * use calculator server 
	 */ 
        result = Calculator_add(server, 1.0, 2.0, ev);
	abort_if_exception(ev, "service not reachable");

	/* prints results to console */
        g_print("Result: 1.0 + 2.0 = %2.0f\n", result);

	/* tear down object reference and ORB */
        CORBA_Object_release(server,ev);
	abort_if_exception(ev, "releasing service failed");

	CORBA_ORB_destroy (orb, ev);
	abort_if_exception(ev, "cleanup failed");

	/* successfull termination */
	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.

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.

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 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__destroy(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__destroy,	/* 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__destroy(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);
}

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 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;
}

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 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"

/** 
 * test for exception */
static
gboolean 
raised_exception(CORBA_Environment *ev) {
	return ((ev)->_major != CORBA_NO_EXCEPTION);
}

/**
 * in case of any exception this macro will abort the process  */
static
void 
abort_if_exception(CORBA_Environment *ev, const char* mesg) 
{
	if (raised_exception (ev)) {
		g_error ("%s %s", mesg, CORBA_exception_id (ev));
		CORBA_exception_free (ev); 
		abort(); 
	}
}

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
calculator_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);
                 abort_if_exception (local_ev, "ORB shutdown failed");
        }
}

/* Inits ORB @orb using @argv arguments for configuration. For each
 * consumed option from vector @argv the counter of @argc_ptr
 * will be decremented. Signal handler is set to call
 * calculator_server_shutdown function in case of SIGINT and SIGTERM
 * signals.  If error occures @ev points to exception object on
 * return.
 */static 
void 
calculator_server_init (int               *argc_ptr, 
			char              *argv[],
			CORBA_ORB         *orb,
			CORBA_Environment *ev)
{
	/* init signal handling */

	signal(SIGINT,  calculator_server_shutdown);
	signal(SIGTERM, calculator_server_shutdown);
	
	/* create Object Request Broker (ORB) */
	
        (*orb) = CORBA_ORB_init(argc_ptr, argv, "orbit-local-orb", ev);
	if (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 
Calculator
calculator_server_activate_service (CORBA_ORB         orb,
				    CORBA_Environment *ev)
{
	Calculator                 servant     = CORBA_OBJECT_NIL; 
	PortableServer_POA         poa         = CORBA_OBJECT_NIL; 
	PortableServer_POAManager  poa_manager = CORBA_OBJECT_NIL; 

        /* get Portable Object Adaptor (POA) */

        poa = 
	 (PortableServer_POA) CORBA_ORB_resolve_initial_references(orb,
								   "RootPOA",
								   ev);
	if (raised_exception(ev)) return CORBA_OBJECT_NIL;

       /* create servant in context of poa container */

	servant = impl_Calculator__create (poa, ev);
	if (raised_exception(ev)) return CORBA_OBJECT_NIL;
	
        /* activate POA Manager */

        poa_manager = PortableServer_POA__get_the_POAManager(poa, ev);
	if (raised_exception(ev)) return CORBA_OBJECT_NIL;

	PortableServer_POAManager_activate(poa_manager, ev);
	if (raised_exception(ev)) return CORBA_OBJECT_NIL;

	return servant;
}

/* Writes stringified object reference of @servant to file-stream
 * @stream. If error occures @ev points to exception object on
 * return.
 */
static 
void 
calculator_server_export_service_to_stream (CORBA_ORB          orb,
					    Calculator         servant,
					    FILE              *stream, 
					    CORBA_Environment *ev)
{
        CORBA_char *objref = NULL;

	/* write objref to file */
	
        objref = CORBA_ORB_object_to_string (orb, servant, ev);
	if (raised_exception(ev)) return;

        /* print ior to terminal */
	fprintf (stream, "%s\n", objref);
	fflush (stream);

        CORBA_free (objref);
}

/* Entering main loop @orb handles incoming request and delegates to
 * servants. If error occures @ev points to exception object on
 * return.
 */
static 
void 
calculator_server_run (CORBA_ORB          orb,
		       CORBA_Environment *ev)
{
        /* enter main loop until SIGINT or SIGTERM */
	
        CORBA_ORB_run(orb, ev);
	if (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 calculator_server_cleanup (CORBA_ORB          orb,
				Calculator         servant,
				CORBA_Environment *ev)
{
	/* releasing managed object */
        CORBA_Object_release(servant, ev);
	if (raised_exception(ev)) return;

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

/* 
 * main 
 */

int
main (int argc, char *argv[])
{
	Calculator servant = CORBA_OBJECT_NIL;

	CORBA_Environment  ev[1];
	CORBA_exception_init(ev);
	
	calculator_server_init (&argc, argv, &global_orb, ev);
	abort_if_exception(ev, "init failed");

	servant = calculator_server_activate_service (global_orb, ev);
	abort_if_exception(ev, "activating service failed");

	calculator_server_export_service_to_stream (global_orb, /* ORB    */ 
						    servant,    /* object */ 
						    stdout,     /* stream */ 
						    ev);   
	abort_if_exception(ev, "exporting IOR failed");
	
	calculator_server_run (global_orb, ev);
	abort_if_exception(ev, "entering main loop failed");

	calculator_server_cleanup (global_orb, servant, ev);
	abort_if_exception(ev, "cleanup failed");

	exit (0);
}

I'm not going to explain every line of this example yet, because we want the server up and make our first calls to it. Though one line deserves some explanation and this is the fprintf(stream, "%s\n",objref) call. The purpose of this call is to print the string representation of the the object reference. This string, which always starts with the magic sequence "IOR:", is the argument to the client program. It identifies a specific object, the server process which hosts it, the location of the server, and the identity of the object in this specific server, because it's possible that one server hosts many objects. How to get such strings or object references without cutting the output of one program (the server) and pasting it into the commandline of another program (the client) will be explained later.

Compiling and Running the Server and the 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 14. makefile

PREFIX=/usr/local
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-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.ior. The server should print a very long string into the file calculator.ior, starting with the 4 character sequence IOR: In the second window we start the client with the command calculator-client `cat calculator.ior` 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.