5.3. Echo client & server

The aim for this example is to run a client which accepts typed input and this is printed out on the server on stdout

The echo functionality is represented in the following very short IDL.

Example 5-1. Echo IDL file


// MyFirstOrbit program - The Echo object
//
// All this does is pass a string from the
// client to the server.

interface Echo {
	void echoString(in string input);
};

The interface definition is the key part of the definition. Each interface defines an object which can have methods on it. In this case there is one method, which takes a string argument and returns nothing. The in declaration before the argument indicates that this arguments is only passed into the method. Generally all arguments are in arguments, for the first case.

This idl is found in the file echo.idl. To compile the idl one does the following step: $ orbit-idl-2 --skeleton-impl echo.idl which will produce most of the needed files to start writing the echo application (you can see it as a framework). They are listed in the following table:

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

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

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

5.3.1. Echo client

The client code is shown here

Example 5-2. echo-client.c

/*
 * Echo client program.. Hacked by Ewan Birney <birney@sanger.ac.uk>
 * from echo test suite, update for ORBit2 by Frank Rehberger
 * <F.Rehberger@xtradyne.de>
 *
 * Client reads object reference (IOR) from local file 'echo.ref' and
 * forwards console input to echo-server. A dot . as single character
 * in input terminates the client.
 */

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

/*
 * This header file was generated from the idl
 */

#include "echo.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(); 
	}
}

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

/**
 *
 */
static
CORBA_Object
client_import_service_from_stream (CORBA_ORB          orb,
				   FILE              *stream,
				   CORBA_Environment *ev)
{
	CORBA_Object obj = CORBA_OBJECT_NIL;
	gchar *objref=NULL;
    
	fscanf (stream, "%as", &objref);  /* FIXME, handle input error */ 
	
	obj = (CORBA_Object) CORBA_ORB_string_to_object (global_orb,
							 objref, 
							 ev);
	free (objref);
	
	return obj;
}

/**
 *
 */
static
CORBA_Object
client_import_service_from_file (CORBA_ORB          orb,
				 char              *filename,
				 CORBA_Environment *ev)
{
        CORBA_Object  obj    = NULL;
        FILE         *file   = NULL;
 
        /* write objref to file */
         
        if ((file=fopen(filename, "r"))==NULL)
                g_error ("could not open %s\n", filename);
    
	obj=client_import_service_from_stream (orb, file, ev);
	
	fclose (file);

	return obj;
}


/**
 *
 */
static
void
client_run (Echo  echo_service,
	    CORBA_Environment        *ev)
{
	char filebuffer[1024+1];

	g_print("Type messages to the server\n"
		"a single dot in line will terminate input\n");
	
	while( fgets(filebuffer,1024,stdin) ) {
		if( filebuffer[0] == '.' && filebuffer[1] == '\n' ) 
			break;
		
		/* chop the newline off */
		filebuffer[strlen(filebuffer)-1] = '\0';
      
		/* using the echoString method in the Echo object 
		 * this is defined in the echo.h header, compiled from
		 * echo.idl */

		Echo_echoString(echo_service,filebuffer,ev);
		if (raised_exception (ev)) return;
	}
}

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

        CORBA_Environment ev[1];
        CORBA_exception_init(ev);

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

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

	echo_service = (Echo) client_import_service_from_file (global_orb,
							       "echo.ref",
							       ev);
        abort_if_exception(ev, "import service failed");

	client_run (echo_service, ev);
        abort_if_exception(ev, "service not reachable");
 
	client_cleanup (global_orb, echo_service, ev);
        abort_if_exception(ev, "cleanup failed");
 
        exit (0);
}
The client can be broken down into three distinct sections.

The key part of the client is when it calls the echoString method on the server. The idl definition

     void echoString(in string input);
Ends up becoming the following definition in the echo.h header file generated from the idl

extern void Echo_echoString(Echo               obj, 
                            CORBA_char        *astring, 
                            CORBA_Environment *ev);
This follows the accepted rules for Object based programming in C, that is

Of course, you don't have to follow this in your own code, but this is how the CORBA C mapping works, and it is not a bad solution.

5.3.2. Echo Server

The server is basically more complicated than the client, but has some commonality with the client. The server has to at the end of the day go into a main loop where it listens to connections. Before that it has to create the ORB and bind its own implementations of the objects to the ORB.

In real life servers, this gets much more complicated, but as this is an example, it is pretty simple once you get through the ORB initialisation process.

Example 5-3. echo-server.c source code

/*
 * echo-server program. Hacked from Echo test suite by
 * <birney@sanger.ac.uk>, ORBit2 udpate by Frank Rehberger
 * <F.Rehberger@xtradyne.de>
 */

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

#include "echo-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)
{
	Echo  ref = CORBA_OBJECT_NIL; 

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

The key part of the server is when it calls servant = impl_Echo__create (poa, ev);. This is a function defined in file echo-skelimpl.c being included at top of echo-server.c. For each object method of echo object interface file echo-skelimpl.c contains a predefined implementation that must be extended by user (specific regions are marked by comments); incoming requests are delegated by object manager to specific method implementation. - For echo server application only a single line for method echoString(..) must be inserted, this line will print the echo-string to console. Let's have a look at echo-skelimpl.c that has been generated by orbit-idl-2 tool as template for user. Therefor only a single line has been added by user g_print ("%s\n", input); at very end of file in function body impl_Echo_echoString(..).

Note

Constructor (__create) and Destructor (__fini) are defined, too. How to extend those functions defining lifecycle of objects will be subject to next chapters.

Example 5-4. echo-skelimpl.c

#include "echo.h"

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

typedef struct
{
   POA_Echo servant;
   PortableServer_POA poa;

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

/*** Implementation stub prototypes ***/

static void impl_Echo__fini(impl_POA_Echo * servant,
			       CORBA_Environment * ev);
static void
impl_Echo_echoString(impl_POA_Echo * servant,
		     const CORBA_char * input, CORBA_Environment * ev);

/*** epv structures ***/

static PortableServer_ServantBase__epv impl_Echo_base_epv = {
   NULL,			/* _private data */
   (gpointer) &amp; impl_Echo__fini,	/* finalize routine */
   NULL,			/* default_POA routine */
};
static POA_Echo__epv impl_Echo_epv = {
   NULL,			/* _private */
   (gpointer) & impl_Echo_echoString,

};

/*** vepv structures ***/

static POA_Echo__vepv impl_Echo_vepv = {
   &impl_Echo_base_epv,
   &impl_Echo_epv,
};

/*** Stub implementations ***/

static Echo
impl_Echo__create(PortableServer_POA poa, CORBA_Environment * ev)
{
   Echo retval;
   impl_POA_Echo *newservant;
   PortableServer_ObjectId *objid;

   newservant = g_new0(impl_POA_Echo, 1);
   newservant->servant.vepv = &impl_Echo_vepv;
   newservant->poa =     
      (PortableServer_POA) CORBA_Object_duplicate((CORBA_Object) poa, ev);
   POA_Echo__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_Echo__fini(impl_POA_Echo * 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_Echo__fini((PortableServer_Servant) servant, ev);

   g_free(servant);
}

static void
impl_Echo_echoString(impl_POA_Echo * servant,
		     const CORBA_char * input, CORBA_Environment * ev)
{
   /* ------   insert method code here   ------ */
   g_print ("%s\n", input); 
   /* ------ ---------- end ------------ ------ */
}

5.3.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 your Linux or BSD/Unix distribution the makefile below will do.

Example 5-5. Makefile


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

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

Example 5-6. Invoking make


[frehberg@papaya echo]$ make
orbit-idl-2 echo.idl
orbit-idl-2 2.4.1 compiling
 small mode, show preprocessor errors, passes: stubs skels common headers 
 skel_impl imodule

gcc -DORBIT2=1 -D_REENTRANT -I/usr/include/orbit-2.0 -I/usr/include/linc-1.0 
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include   -c -o echo-client.o   
echo-client.c
gcc -DORBIT2=1 -D_REENTRANT -I/usr/include/orbit-2.0 -I/usr/include/linc-1.0 
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include   -c -o echo-common.o   
echo-common.c
gcc -DORBIT2=1 -D_REENTRANT -I/usr/include/orbit-2.0 -I/usr/include/linc-1.0 
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include   -c -o echo-stubs.o     
echo-stubs.c
gcc -Wl,--export-dynamic -lORBit-2 -llinc -lgmodule-2.0 -ldl -lgobject-2.0   
-lgthread-2.0 -lpthread -lglib-2.0 -lm  echo-client.o echo-common.o          
echo-stubs.o   -o echo-client
gcc -DORBIT2=1 -D_REENTRANT -I/usr/include/orbit-2.0 -I/usr/include/linc-1.0 
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include   -c -o echo-server.o    
echo-server.c
gcc -DORBIT2=1 -D_REENTRANT -I/usr/include/orbit-2.0 -I/usr/include/linc-1.0 
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include   -c -o echo-skels.o     
echo-skels.c
gcc -Wl,--export-dynamic -lORBit-2 -llinc -lgmodule-2.0 -ldl -lgobject-2.0  
-lgthread-2.0 -lpthread -lglib-2.0 -lm  echo-server.o echo-common.o          
echo-skels.o   -o echo-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: ./echo-server. The server should print a very long string into the file echo.ref, starting with the 4 character sequence IOR: In the second window we will print content of echo.ref to console and start the client with the command ./echo-client. You should not try to type the IOR string, instead use the cut and paste functionality of your terminal.

Example 5-7. Terminal 1 - Starting Echo Server


[frehberg@papaya echo]$ ./echo-server

Example 5-8. Terminal 2 - Starting Echo Client


[frehberg@papaya echo]$ cat echo.ref
IOR:010000000d00000049444c3a4563686f3a312e3000000000030000000054424f540000000101
020005000000554e4958000000000700000070617061796100002e0000002f746d702f6f72626974
2d66726568626572672f6c696e632d323230662d302d323532356663323537306430340000000000
0000caaedfba58000000010102002e0000002f746d702f6f726269742d66726568626572672f6c69
6e632d323230662d302d323532356663323537306430340000001c00000000000000a6361450d7ea
e8a8dc29282828282828010000008af91bdf01000000480000000100000002000000050000001c00
000000000000a6361450d7eae8a8dc29282828282828010000008af91bdf01000000140000000100
000001000105000000000901010000000000
[frehberg@papaya echo]$ ./echo-client
Type messages to the server
a single dot in line will terminate input: