5.5. Account client & server

In this third simple example, we will see how we can set up a client that can modify the value of a variable stored on the server. It is basically the way an account manager works. The stored variable here is balance. The idl definition (account.idl) for our account is :

Example 5-15. account.idl


   interface Account {
      void deposit (in unsigned long amount);
      void withdraw (in unsigned long amount);
      readonly attribute long balance;
   };
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 account.idl geenrates all the files we will use in this example.

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

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

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

5.5.1. Account client

There is no difficulty in setting the client (at least no more than in the previous examples). Only one thing has been added : we test for the availabilty of the server (if (!acc_client) ...) before invoking calls to the server.

Example 5-16. account-client.c

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

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

#include "account.h"

#include "examples-toolkit.h" /* ie. etk_abort_if_exception() */ 

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 (Account            service,
	    CORBA_long         amount,
	    CORBA_Environment *ev)
{
	CORBA_long balance=0;

	/*
         * use calculator server
         */
         
         balance = Account__get_balance (service, ev); 
	if (etk_raised_exception (ev)) return;
         
        g_print ("balance %5d, ", balance);
         
        if (amount > 0)
        {
                 Account_deposit (service, amount, ev); 
                if (etk_raised_exception (ev)) return;
        }
        else
        {
                 Account_withdraw (service, abs(amount), ev); 
                if (etk_raised_exception (ev)) return;
        }
                                                                               
         balance = Account__get_balance (service, ev); 
	if (etk_raised_exception (ev)) return;
                                                                               
        g_print ("new balance %5d\n", balance);
}

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

	Account 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");

        if (argc<2) g_error ("usage: %s <amount>", argv[0]);

        amount  = atoi(argv[1]);

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

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

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


5.5.2. Account server

5.5.2.1. Account server Skeleton Implementation

For the server, like in the previous example, we first generate the source file account-skelimpl.c that will receive the implementation code for the methods. This is done once again with orbit-idl-2 --skeleton-impl account.idl.

Now, let us edit account-skelimpl.c. We search for the the balance attribute that was declared in the IDL file. At the beginning of the file, we can spot the way it has been translated into C by the idl compiler:

Example 5-17. account-skelimpl.c fragment - object declaration

typedef struct
{
   POA_Account servant;
   PortableServer_POA poa;

   CORBA_long attr_balance;

   /* ------ add private attributes here ------ */
   CORBA_long attr_balance;
   /* ------ ---------- end ------------ ------ */
}
impl_POA_Account;
So, the server methods (withdraw and deposit) will have to manage the balance of the account through the servant->attr_balance (the servant variable is passed as parameter to each method).

Now, let us get to the end of the file and find the methods stubs. We find the impl_Account_* functions, to which we add the implementation code. This could be:

Example 5-18. account-skelimpl.c fragment - method definition

static void
impl_Account_deposit(impl_POA_Account * servant,
		     const CORBA_unsigned_long amount, CORBA_Environment * ev)
{
   /* ------   insert method code here   ------ */
   servant->attr_balance += amount;
   /* ------ ---------- end ------------ ------ */
}

static void
impl_Account_withdraw(impl_POA_Account * servant,
		      const CORBA_unsigned_long amount,
		      CORBA_Environment * ev)
{
   /* ------   insert method code here   ------ */
   servant->attr_balance -= amount;
   /* ------ ---------- end ------------ ------ */
}

static CORBA_long
impl_Account__get_balance(impl_POA_Account * servant, CORBA_Environment * ev)
{
   CORBA_long retval;

   /* ------   insert method code here   ------ */
   retval = servant->attr_balance;
   /* ------ ---------- end ------------ ------ */

   return retval;
}

The missing key stone is the constructor that establishs initial, consistent state for object on creation.

Example 5-19. account-skelimpl.c fragment - constructor

...
static Account
impl_Account__create(PortableServer_POA poa, CORBA_Environment * ev)
{
   Account retval;
   impl_POA_Account *newservant;
   PortableServer_ObjectId *objid;

   newservant = g_new0(impl_POA_Account, 1);
   newservant->servant.vepv = &impl_Account_vepv;
   newservant->poa =       
      (PortableServer_POA) CORBA_Object_duplicate((CORBA_Object) poa, ev);
   POA_Account__init((PortableServer_Servant) newservant, ev);
   /* Before servant is going to be activated all
    * private attributes must be initialized.  */

   /* ------ init private attributes here ------ */
   newservant->attr_balance = 0;
   /* ------ ---------- end ------------- ------ */

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

   return retval;
}
..

5.5.2.2. Account server implementation

Lastly, we have to write a rather generic code to set up the server. We call it account-server.c. It is roughly the same code as in the calculator and echo examples. The code just initializes the ORB and publishes an IOR for the server object.

Example 5-20. account-server.c

/*
 * account-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 "account.h"
#include "account-skelimpl.c"

#include "examples-toolkit.h" /* provides etk_abort_if_exception */ 

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)
{
	Account ref = CORBA_OBJECT_NIL; 

	 ref = impl_Account__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[] = "account.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);
}
	


5.5.3. Compiling server & client

The Makefile is the roughly the same as the one in the Calculator example. By now the schema should be clear and you should be able to reuse this Makefile for numerous small projects.

Example 5-21. Makefile for the Account example

PREFIX ?= /usr
CC = gcc
TARGETS=account-client account-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=account-common.c account-stubs.c account-skels.c account.h
 
all: $(IDLOUT) account-client account-server
 
account-server.o : account-server.c account-skelimpl.c

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