Developing a CORBA Server

84
Developing a CORBA server Use this task to develop a CORBA server to service requests for business functions used in the implementation of client objects. The instructions and code extracts provided in this task are based on the development of the WSLoggerServer sample, for which files are included with WebSphere in the following directory: WAS_HOME/Enterprise/samples/sampcppsdk. Developing a CORBA server involves developing a server implementation class (known as a servant) and a server, as described in the following steps: 1. Create and edit an IDL file, servant.idl, to specify the public interface to the servant object class; where servant is the name of the server implementation class. For more information about creating and editing an IDL file for the servant object class, see Defining the interface for a servant implementation (servant.idl). This steps results in a fully-specified servant.idl file. 2. Compile the servant IDL file,servant.idl, to produce the usage binding files needed to implement and use the servant object within a particular programming language. For more information about compiling an IDL file, see Compiling the servant IDL (using idlc). This step results in the set of usage binding files required for the servant.idl file. 3. Add declarations for class variables, constructors, and destructors to the servant implementation header (servant.ih). For more information about adding declarations to an implementation header, see Adding declarations to a CORBA servant implementation header (servant.ih). This step results in the servant implementation header file, servant.idl, that contains all the declarations for class variables, constructors, and destructors needed by the implementation. 4. Complete the servant implementation servant_I.cpp, to add the code that is to implement the servant business logic. For more information about completing the servant implementation, see Adding code to a CORBA servant implementation (servant_I.cpp). This step results in the server implementation file, servant_I.cpp, that contains the code needed by the implementation to support the business logic. 5. Create the server main, server.cpp, to write the code for the methods that the server implements (for example, to perform initialization tasks and create servant objects). For more information about completing the servant implementation, see Creating the server main code (server.cpp). This step results in the server main source file, server.cpp, that contains the main method and associated code needed to implement the server.

Transcript of Developing a CORBA Server

Page 1: Developing a CORBA Server

Developing a CORBA server

Use this task to develop a CORBA server to service requests for business functions used in the implementation of client objects. The instructions and code extracts provided in this task are based on the development of the WSLoggerServer sample, for which files are included with WebSphere in the following directory: WAS_HOME/Enterprise/samples/sampcppsdk.

Developing a CORBA server involves developing a server implementation class (known as a servant) and a server, as described in the following steps:

1. Create and edit an IDL file, servant.idl, to specify the public interface to the servant object class; where servant is the name of the server implementation class.

For more information about creating and editing an IDL file for the servant object class, see Defining the interface for a servant implementation (servant.idl).

This steps results in a fully-specified servant.idl file.

2. Compile the servant IDL file,servant.idl, to produce the usage binding files needed to implement and use the servant object within a particular programming language.

For more information about compiling an IDL file, see Compiling the servant IDL (using idlc).

This step results in the set of usage binding files required for the servant.idl file.

3. Add declarations for class variables, constructors, and destructors to the servant implementation header (servant.ih).

For more information about adding declarations to an implementation header, see Adding declarations to a CORBA servant implementation header (servant.ih).

This step results in the servant implementation header file, servant.idl, that contains all the declarations for class variables, constructors, and destructors needed by the implementation.

4. Complete the servant implementation servant_I.cpp, to add the code that is to implement the servant business logic.

For more information about completing the servant implementation, see Adding code to a CORBA servant implementation (servant_I.cpp).

This step results in the server implementation file, servant_I.cpp, that contains the code needed by the implementation to support the business logic.

5. Create the server main, server.cpp, to write the code for the methods that the server implements (for example, to perform initialization tasks and create servant objects).

For more information about completing the servant implementation, see Creating the server main code (server.cpp).

This step results in the server main source file, server.cpp, that contains the main method and associated code needed to implement the server.

Build the server code, as described in Building a C++ CORBA server. Adding declarations to a CORBA servant class definition (servant.ih)

Use this task to add declarations for class variables, constructors, and destructors for a CORBA servant class to its skeleton implementation header file, servant.ih. This defines any private variables for the implementation code in the associated servant_I.cpp file.

This task follows on from the task to compile the servant.idl file that defines the public interface for the server implementation class. For more information about compiling the IDL file, which creates the servant.ih file, see Compiling the servant IDL (using idlc).

To add declarations for class variables, constructors, and destructors to an implementation header file,servant.ih, complete the following steps:

Page 2: Developing a CORBA Server

1. At a command line change to the directory that contains the servant.ih file, where servant is the name of the servant class.

2. Edit the implementation header file, servant.ih, to add appropriate declarations for class variables, constructors, and destructors. For more information about the types of declarations that you can add to an implementation header file, see IDL type declarations.

For example, the idlc command idlc -ehh:ih:ic:uc:sc -mdllname=WSLogger WSLogger.idl converted the following interface declaration to the class declaration in the WSLogger.ih file. The WSLogger.ih file was then edited to add the extra declarations shown in bold.

You can next add code to skeleton implementation definition, servant_I.cpp, to implement the business logic, as described in Completing the server implementation (server_I.cpp).

Adding code to a CORBA servant implementation (servant_I.cpp)

Use this task to add code for a CORBA server implementation class to its skeleton implementation file, servant_I.cpp. The code defines the methods that implement the business logic for the server implementation class, servant.

This task follows on from the task to add declarations for class variables, constructors, and destructors to the servant implementation header file,servant.ih. For more information about adding declarations to an implementation header, see Adding declarations to a CORBA servant implementation header (servant.ih).

To add code to an implementation file,servant_I.cpp, to add the business logic that the servant is to provide, complete the following steps:

Example: WSLogger interface and declarations added to the skeleton implementation headerInterface declaration in WSLogger.idl

Implementation header in WSLogger.ih

interface WSLogger {

void setFileName(in string newFileName);

string getFileName();

void setMethodName( in string newMethodName );

string getMethodName();

short openLogFile();

short closeLogFile();

short writeLogMessage(in string newMessage, in short newSeverity);

const short DMY_DATE_FORMAT = 1;

const short MDY_DATE_FORMAT = 2;

void setDateFormat(in unsigned short newDateFormat);

unsigned short getDateFormat();

};

class WSLogger_Impl : public

virtual ::WSLogger_Skeleton {

public:

::CORBA::Void setFileName (const char*

newFileName);

char* getFileName ();

::CORBA::Void setMethodName (const char*

newMethodName);

char* getMethodName ();

::CORBA::Short openLogFile ();

::CORBA::Short closeLogFile ();

::CORBA::Short writeLogMessage (const char*

newMessage, ::CORBA::Short newSeverity);

::CORBA::Void setDateFormat (::CORBA::UShort

newDateFormat);

::CORBA::UShort getDateFormat ();

private:

char * fileName;

char * methodName;

::CORBA::UShort dateFormat;

ofstream logFile;

::CORBA::UShort logFileOpen;

public:

WSLogger_Impl( char * newFileName );

virtual ~WSLogger_Impl();

};

Page 3: Developing a CORBA Server

1. At a command line change to the directory that contains the servant_I.cpp file, where servant is the name of the server implementation class.

2. Edit the implementation file, servant_I.cpp, to add appropriate code to implement the business logic methods.

For example, the idlc command idlc -ehh:ih:ic:uc:sc -mdllname=WSLogger WSLogger.idl converted the following interface declaration to the skeleton methods in the implementation file, WSLogger_I.cpp. The WSLogger_I.cpp file was then edited to add the code to implement the methods, with the code added for the WSLogger_Impl::writeLogMessage method shown in bold.

::CORBA::Void WSLogger_Impl::setFileName (const char* newFileName)

{

}

char* WSLogger_Impl::getFileName ()

{

}

::CORBA::Void WSLogger_Impl::setMethodName (const char* newMethodName)

{

}

char* WSLogger_Impl::getMethodName ()

{

}

::CORBA::Short WSLogger_Impl::openLogFile ()

{

}

::CORBA::Short WSLogger_Impl::closeLogFile ()

{

}

// This method writes one line of message text to the log file. The line

// prefaced with the current date and time in the currently specified

// format, the current method name (if any), the severity level, and

// the message text.

::CORBA::Short WSLogger_Impl::writeLogMessage (const char* newMessage, ::CORBA::Short newSeverity)

{

::CORBA::String_var timeString;

if ( logFileOpen == FALSE )

return( -1 );

// Get the date and time string.

time_t tp;

time_t tp2;

if ( ( tp = time(&tp2) ) != -1 )

{

struct tm *x = gmtime( &tp2 );

timeString = ::CORBA::string_dup( ctime( &tp2 ) );

}

// Determine the day and month.

::CORBA::String_var day = ::CORBA::string_alloc( 3 );

::CORBA::String_var month = ::CORBA::string_alloc( 4 );

day[0] = timeString[8];

day[1] = timeString[9];

day[2] = 0;

month[0] = timeString[4];

Page 4: Developing a CORBA Server

month[1] = timeString[5];

month[2] = timeString[6];

month[3] = 0;

// Copy the time and year.

::CORBA::String_var time = ::CORBA::string_alloc( 14 );

strncpy( time, (const char *) &timeString[11], 13 );

time[13] = 0;

// Output the time of the log message.

if ( dateFormat == DMY_DATE_FORMAT )

logFile << day << " " << month;

else if ( dateFormat == MDY_DATE_FORMAT )

logFile << month << " " << day;

logFile << " " << time << ", ";

if ( getMethodName() != NULL )

logFile << getMethodName() << ", ";

logFile << "severity " << newSeverity << ": ";

// Output the log message.

logFile << newMessage << endl;

return 0;

}

::CORBA::Void WSLogger_Impl::setDateFormat (::CORBA::UShort newDateFormat)

{

}

::CORBA::UShort WSLogger_Impl::getDateFormat ()

{

}

You can next create the server main code (server.cpp), to implement the server, as described in Creating a CORBA server main code (server.cpp).

6.

Creating the CORBA server main code (server.cpp)

Use this task to create a CORBA server that hosts a servant object. The server performs the following tasks

1. Validating user input

2. Initializing the server environment

3. Accessing naming contexts

4. Naming, creating, and binding a servant object

5. Creating a server shutdown object

6. Going into a wait loop

7. Servicing requests

This task follows on from adding code for the business logic methods in the servant implementation file,servant_I.cpp. For more information about adding code to a servant implementation file, see Completing the servant implementation (servant_I.cpp).

To create the main code for a CORBA server, complete the following steps:

Page 5: Developing a CORBA Server

1. Create a source file,servantServer.cpp, where servant is the name of the implementation class for which the server is to service requests.

2. Edit the server source file, servantServer.cpp, to add appropriate code to implement the server methods. To do this, complete the following steps:

1. Add include statements and global declarations needed, as described in Creating CORBA server main code (server.cpp), adding include statements and global declarations.

2. Add the main method, in the form:

3. void main( int argc, char *argv[] )

4. {

5. ::CORBA::Object_ptr objPtr;

6. ::CORBA::Status stat;

7. int rc = 0;

8. }

3. Edit the server source file, servantServer.cpp, to add appropriate code to check the input parameters provided on the command used to start the server, as described in Creating CORBA server main code (server.cpp), adding code to check input parameters.

4. Edit the server source file, servantServer.cpp, to add appropriate code to initialize the server environment, as described in Creating CORBA server main code (server.cpp), adding code to initialize the server environment.

5. Edit the server source file, servantServer.cpp, to add appropriate code to access naming contexts, as described in Creating CORBA server main code (server.cpp), adding code to access naming contexts.

At this point initialization has been accomplished and a naming context created for servant objects.

6. Edit the server source file, servantServer.cpp, to add appropriate code to name, create, and bind servant objects, as described in Creating CORBA server main code (server.cpp), adding code to name, create, and bind servant objects.

7. Edit the server source file, servantServer.cpp, to add code to create a server shutdown object, as described in Creating CORBA server main code (server.cpp), adding code to create a server shutdown object.

8. Edit the server source file, servantServer.cpp, to add code to put the server into an infinite loop (to service any ORB requests received), as described in Creating CORBA server main code (server.cpp), adding code to put the server into an infinite loop.

9. Edit the server source file, servantServer.cpp, to add code to shutdown the server and release resources used, as described in Creating CORBA server main code (server.cpp), adding code to shutdown the server and release resources used.

Building a C++ CORBA server

This topic provides an overview of the task to build the code for a C++ CORBA server. The actual steps that you complete depend on the development environment that you use.

For example, if you are using the Microsoft C++ 6.0 compiler on Windows NT to build a C++ CORBA server, you can use the following commands:

1. Compile the server.cpp, servant_I.cpp, and servant_S.cpp files.

At a command line, type the following command for each file:

-DSOMCBNOLOCALINCLUDES -D_MS_INC_DIR=msvc60_install\include

-D_USE_NAMESPACE -DNO_STRICT -Imsvc60_install\include -Imsvc60_install\include

-Iwasee_install\include -I. filename

Where

msvc60_install

is directory in which the Microsoft C++ 6.0 compiler is installed; for example, d:\msvc60\vc98.

wasee_install

is the directory into which WebSphere Application Server enterprise services is installed.

filename

Page 6: Developing a CORBA Server

is the name of the file to be compiled (server.cpp, servant_I.cpp, or servant_S.cpp).

2. link /nologo /DEBUG /OUT:WSLoggerServer.exe

3. /DEFAULTLIB:\WebSphere\AppServer\Enterprise\lib\somosa1m.lib

4. \WebSphere\AppServer\Enterprise\lib\somororm.lib \WebSphere\AppServer\Enterprise\lib\somsrvsm.lib

5. servantServer.obj servant_I.obj servant.obj

This task is one step of the parent task, Developing a CORBA client.

For more examples of building C++ CORBA server code (on several platforms) for WebSphere Application Server, see the samples article "Tutorial: Creating a user-defined C++ server and client" at WAS_HOME/Enterprise/samples/sampeex/sampcppsdk/wsBuildLogger.htm.

Related tasks...

Parent: Developing a CORBA server

Related concepts...

The CORBA server programming model

The CORBA programming model

A Simple C++ Client/Server in CORBA

By Carlos Jiménez de Parga | 27 Sep 2009

An introduction to the Visual C++ CORBA development

Top of Form

Download source files - 29.22 KB

Contents

Introduction

1.1 Interface Definition Language (IDL)

1.2 Mapping IDL to a Programming Language

/w EPDw UKMTAy

Page 7: Developing a CORBA Server

1.3 IDL Compilers

1.4 Making a Remote Call

1.5 CORBA Services

Developing a Simple Client-server Application

2.1 The Business Logic Domain

2.2 Writing the IDL

2.3 Generating the Skeleton and the Stub

2.4 Implementing the Servant Class

2.5 Creating the Server

2.6 Implementing the Client

2.7 Running the Client-server Application

CORBA Benefits and Drawbacks

Further Reading

Acknowledgements

1 Introduction

The Object Management Group is a not-for-profit organization that promotes the use of object-oriented technologies. Among other things, it defines the UML and CORBA

Page 8: Developing a CORBA Server

standards.

CORBA is an acronym for Common ORB Architecture. The phrase common architecture means a technical standard, so CORBA is simply a technical standard for something called an ORB.

In turn, ORB is an acronym for object request broker, which is an object-oriented version of an older technology called remote procedure call (RPC). An ORB or RPC is a mechanism for invoking operations on an object (or calling a procedure) in a different (“remote”) process that may be running on the same, or a different, computer. At a programming level, these “remote” calls look similar to “local” calls. In fact, CORBA makes it possible for a client application written in one programming language, say, Java, to make a remote call to a server implemented in a different programming language, say, C++. This language

Page 9: Developing a CORBA Server

independence arises because the public interfaces of a server application are defined in an IDL file and CORBA defines mappings from IDL to many programming languages, including C, C++, Java, COBOL, Ada, SmallTalk and Python.

1.1 Interface Definition Language (IDL)

An IDL file defines the public application programming interface (API) that is exposed by objects in a server application. The type of a CORBA object is called an interface, which is similar in concept to a C++ class or a Java interface. An example IDL file is shown below.

Collapse | Copy Code

module Finance {

typedef sequence<string> StringSeq;

struct AccountDetails {

string name;

Page 10: Developing a CORBA Server

StringSeq address;

long account_number;

double current_balance;

};

exception insufficientFunds { };

interface Account {

void deposit(in double amount);

void withdraw(in double amount) raises(insufficientFunds);

readonly attribute AccountDetails details;

};

};

As the above example shows, IDL types may be grouped into a module. This construct serves a purpose similar to a C++ namespace or a Java package: it places a prefix on the names of types to prevent namespace pollution. The scoping operator in IDL is “::”. For example, Finance::Account is the fully-scoped name of the Account type

Page 11: Developing a CORBA Server

defined in the Finance module.

An IDL interface may contain operations and attributes (and, if you want, also nested types). Many people mistakenly assume that an attribute is similar in concept to an instance variable in C++ (a field in Java). This is wrong. An attribute is simply syntactic sugar for a pair of get- and set-style operations. An attribute can be readonly, in which case it maps to just a get-style operation.

The parameters of an operation have a specified direction, which can be in (meaning that the parameter is passed from the client to the server), out (the parameter is passed from the server back to the client) or inout (the parameter is passed in both directions). Operations can also have a return value. An operation can raise (throw) an exception if something goes wrong. There are over 30 predefined exception types,

Page 12: Developing a CORBA Server

called system exceptions, that all operations can throw, although in practice system exceptions are raised by the CORBA runtime system much more frequently than by application code. In addition to the pre-defined system exceptions, new exception types can be defined in an IDL file. These are called user-defined exceptions. A raises clause on the signature of an operation specifies the user-defined exceptions that it might throw.

Parameters to an operation (and the return value) can be one of the built-in types—for example, string, boolean or long—or a “user-defined” type that is declared in an IDL file. User-defined types can be any of the following:

A struct. This is similar to a C/C++ struct or a Java class that contains only public fields.

A sequence. This is a collection type. It is like a one-dimensional

Page 13: Developing a CORBA Server

array that can grow or shrink. The IDL-to-C++ mapping predates the C++ standard template library so, unfortunately, an IDL sequence does not map to a std::vector. Instead, the IDL-to-C++ mapping defines its own vector-like data type.

An array. The dimensions of an IDL array are specified in the IDL file, so an array is of fixed size, that is, it cannot grow or shrink at runtime. Arrays are rarely used in IDL. The sequence type is more flexible and so is more commonly used.

A typedef. This defines a new name for an existing type. For example, the statement below defines age that is represented as a short:

Collapse | Copy Code

typedef short age;

A common, and very important, use of typedef is to associate a name with a sequence or array declaration. For

Page 14: Developing a CORBA Server

example:

Collapse | Copy Code

typedef sequence<string> StringSeq;

A union. This type can hold one of several values at runtime, for example:

Collapse | Copy Code

union Foo switch(short) {

case 1: boolean boolVal;

case 2: long longVal;

case 3: string stringVal;

};

An instance of Foo could hold a boolean, a long or a string. The case label indicates which value is currently active.

An enum is conceptually similar to a collection of constant integer declarations. For example:

Collapse | Copy Code

enum color {red,

Page 15: Developing a CORBA Server

green, blue};

Internally, CORBA uses integer values to represent different enum values.

1.2 Mapping IDL to a Programming Language

As already mentioned, IDL is used to define the public API that is exposed by objects in a server application. IDL defines this API in a way that is independent of any particular programming language. However, for CORBA to be useful, there must be a mapping from IDL to a particular programming language. For example, the IDL-to-C++ mapping enables people to develop CORBA applications in C++, while the IDL-to-Java mapping enables people to develop CORBA applications in Java.

The CORBA standard currently defines mappings from IDL to the following programming languages: C, C++, Java, Ada, Smalltalk, COBOL, PL/I, LISP,

Page 16: Developing a CORBA Server

Python and IDLScript. These officially-endorsed language mappings provide source-code portability of applications across different CORBA products. There are unofficial mappings for a few other languages, such as Eiffel, Tcl and Perl.

1.3 IDL Compilers

An IDL compiler translates IDL definitions (for example, struct, union, sequence and so on) into similar definitions in a programming language, such as C++, Java, Ada or Cobol. In addition, for each IDL interface, the IDL compiler generates both stub code—also called proxy types—and skeleton code. These terms often cause confusion, so I explain them below:

A dictionary definition of stub is “the short end remaining after something bigger has been used up, for example, a pencil stub or a cigarette stub”. In traditional (non-distributed)

Page 17: Developing a CORBA Server

programming, a stub procedure is a dummy implementation of a procedure that is used to prevent “undefined label” errors at link time. In a distributed middleware system like CORBA, remote calls are implemented by the client making a local call upon a stub procedure/object. The stub uses an inter-process communication mechanism (such as TCP/IP sockets) to transmit the request to a server process and receive back the reply.

The term proxy is often used instead of stub. A dictionary definition of proxy is “a person authorized to act for another”. A CORBA proxy is simply a client-side object that acts on behalf of the “real” object in a server process. When the client application invokes an operation on a proxy, the proxy uses an inter-process communication mechanism to transmit the request to the “real” object in a server process; then the proxy waits to

Page 18: Developing a CORBA Server

receive the reply and passes back this reply to the application-level code in the client.

The term skeleton code refers to the server-side code for reading incoming requests and dispatching them to application-level objects. The term skeleton may seem like a strange choice. However, use of the word skeleton is not limited to discussions about bones; more generally, it means a “supporting infrastructure”. Skeleton code is so called because it provides supporting infrastructure that is required to implement server applications.

A CORBA product must provide an IDL compiler, but the CORBA specification does not state what is the name of the compiler or what command-line options it accepts. These details vary from one CORBA product to another.

1.4 Making a Remote

Page 19: Developing a CORBA Server

Call

CORBA uses the term servant to refer to an object written in a programming language (for example, C++ or Java) that implements an IDL interface. The diagram below shows what happens when a client application invokes an operation on an object/servant in a server process.

The application code in the client makes an invocation upon a local proxy object (recall that the proxy class is generated by the IDL compiler). The proxy marshals information about the request—such as the name of the operation being invoked, and its in and inout parameters—into a binary buffer. The proxy object then passes this binary buffer to the ORB library (provided with the CORBA product),

Page 20: Developing a CORBA Server

which transmits the request message across the network to the server process. The ORB in the client process waits to read a reply message from the server process. The ORB returns the reply buffer back to the proxy object, which unmarshals inout and out parameters and the return value (or a raised exception), and returns these to the client application code.

On the server side, a thread inside the ORB sits in an event loop, waiting for incoming requests. When a request arrives, the ORB reads the request’s binary buffer and passes this to some code that unmarshals the parameters and dispatches the request to the target servant. The code that performs the unmarshalling and dispatching is spread over two components. One component is called a POA, and I will discuss that later in this article. The other component is the skeleton code that

Page 21: Developing a CORBA Server

was generated by the IDL compiler. The generated skeleton code is not explicitly shown in the diagram because it takes the form of a base class that is inherited by the servant class. When the operation in the servant returns, the skeleton code marshals the inout and out parameters (or a raised exception) into a binary buffer and this is returned via the POA to the server-side ORB, which transmits the reply message across the network to the client process.

1.5 CORBA Services

Many programming languages are equipped with a standardized library of functions and/or classes that complement the core language. These standardized libraries usually provide collection data-types (for example, linked lists, sets, hash tables and so on), file input-output and other functionality that is useful for the development of a wide variety of

Page 22: Developing a CORBA Server

applications. If you asked a developer to write an application in, say, Java, C or C++ but without making use of that language’s standard library, then the developer would find it very difficult.

A similar situation exists for CORBA. The core part of CORBA (an object-oriented RPC mechanism built with IDL and common on-the-wire protocols) is of limited use by itself—in the same way that a programming language stripped of its standardized library is of limited use. What greatly enhances the power of CORBA is a standardized collection of services—called CORBA Services—that provide functionality useful for the development of a wide variety of distributed applications. The CORBA Services have APIs that are defined in IDL. In effect, you can think of the CORBA Services as being like a standardized class library. However, one

Page 23: Developing a CORBA Server

point to note is that most CORBA Services are provided as prebuilt server applications rather than as libraries that are linked into your own application. Because of this, the CORBA Services are really a distributed, standardized class library.

Some of the commonly-used CORBA Services include the following:

The Naming Service and Trading Service allow a server application to advertise its objects, thereby making it easy for client applications to find the objects.

Most CORBA applications use synchronous, one-to-one communication. However, some applications require many-to-many, asynchronous communication, or what many people call publish and subscribe communication. Various CORBA Services have been defined to support this type of communication.

Page 24: Developing a CORBA Server

In a distributed system, it is sometimes desirable for a transaction to span several databases so that when a transaction is committed, it is guaranteed that either all the databases are updated or none are updated. The Object Transaction Service (OTS) provides this capability.

2 Developing a Simple Client-server Application

I have used the Orbacus implementation of COBRA to develop a sample client-server application. Orbacus is available for both Java and C++.

2.1 The Business Logic Domain

My demonstration server application provides encrypt() and decrypt() operations. The client application invokes encrypt() on an object in the server to encrypt some plain text. Later the client invokes decrypt() to decrypt the text again.

The

Page 25: Developing a CORBA Server

encryption/decryption algorithm is based on the Caesar cipher which is one of the simplest encryption techniques. It is a substitution cipher in which each letter in the plain text is replaced by a letter that is a fixed number of positions down the alphabet. I added an XOR operation with an encryption key after the shift operation to obscure the relationship. You can see the substitution technique in the diagram below:

2.2 Writing the IDL

The first step in implementing the client-server application is to write an IDL file that defines the public API of the server. This consists of one interface that provides encrypt() and decrypt() operations. The interface also contains a shutdown() operation so we can gracefully ask the

Page 26: Developing a CORBA Server

server to terminate.

The IDL for the demonstration application is shown below:

Collapse | Copy Code

// Crypt.idl

interface CaesarAlgorithm {

typedef sequence<char> charsequence;

charsequence encrypt(in string info,in unsigned long k,in unsigned long shift);

string decrypt(in charsequence info,in unsigned long k,in unsigned long shift);

boolean shutdown();

};

2.3 Generating the Skeleton and the Stub

After writing the IDL file, we convert the IDL definitions into corresponding C++ definitions by running the IDL compiler supplied with Orbacus.

Collapse | Copy

Page 27: Developing a CORBA Server

Code

idl crypt.idl

Running that command generates the following files:

crypt.h and crypt.cpp: These files define and implement the C++ types corresponding to IDL types (such as structs, unions, sequences and so on) defined in the IDL file. These files also implement the client-side proxy classes corresponding to IDL interfaces.

crypt_skel.h and crypt_skel.cpp: These files define and implement the server-side functionality required to read incoming requests and dispatch them to servants (the C++ objects that represent the CORBA objects).

Note that the CORBA specification has not standardized the name of the IDL compiler, and different CORBA implementations may use different names. For example, the IDL compilers in Orbacus, Orbix and omniORB are called idl, while

Page 28: Developing a CORBA Server

TAO calls its IDL compiler tao_idl, and both VisiBroker and Orbit call theirs idl2cpp.

2.4 Implementing the Servant Class

The next step is to implement the servant class, that is, a C++ class that implements an IDL interface. To do this, we create a class (CryptographicImpl) that inherits from the skeleton class (POA_CaesarAlgorithm) generated by the IDL compiler. You can see this below.

Collapse | Copy Code

#include <iostream>

#include <string>

#include "OB/CORBA.h"

#include "crypt_skel.h"

class CryptographicImpl : virtual public ::POA_CaesarAlgorithm,

Page 29: Developing a CORBA Server

virtual public PortableServer::RefCountServantBase

{

CORBA::ORB_var orb; // Reference to CORBA ORB

public:

CryptographicImpl(CORBA::ORB_var orb)

{

this->orb = orb;

}

// Caesar text encryption algorithm

virtual ::CaesarAlgorithm::charsequence*

encrypt(const char* info,::CORBA::ULong k,::CORBA::ULong shift)

Page 30: Developing a CORBA Server

throw(::CORBA::SystemException)

{

std::string msg = info;

int len = msg.length();

::CaesarAlgorithm::charsequence* outseq =

new ::CaesarAlgorithm::charsequence;

outseq->length(len + 1);

std::string::iterator i = msg.begin();

std::string::iterator end = msg.end();

int j = 0;

while (i != end)

{

Page 31: Developing a CORBA Server

*i+= shift;

*i ^= k;

(*outseq)[j++] = *i++;

}

(*outseq)[len] = '\0';

return outseq;

}

// Caesar text decryption algorithm

virtual char* decrypt(const ::CaesarAlgorithm::charsequence&

info,::CORBA::ULong k,::CORBA::ULong shift)

throw(::CORBA::SystemException)

Page 32: Developing a CORBA Server

{

char* r = CORBA::string_alloc(info.length());

for (int i = 0;i < info.length() - 1;i++)

{

r[i] = info[i];

r[i] ^= k;

r[i] -= shift;

}

r[info.length() - 1] = '\0';

return r;

}

// Terminate CORBA message

virtual ::CORBA::Boolean

Page 33: Developing a CORBA Server

shutdown() throw(::CORBA::SystemException)

{

orb->shutdown(false);

return true;

}

};

The servant implements the operations defined in the IDL interface. When a client makes a remote call, the dispatch code (implemented in the inherited POA_CaesarAlgorithm class) unmarshals the parameters of the incoming request and invokes the operation.

2.5 Creating the Server

Having implemented the servant class, we must now implement the server mainline. This performs some CORBA initialization steps, creates the servant, advertises its object reference in the naming service and then enters an event loop to wait for

Page 34: Developing a CORBA Server

incoming requests. Before showing the code, there are two more CORBA concepts that I need to explain: POA and POA Manager.

From reading this article so far, you might have the impression that a client invokes operations on remote objects, and those remote objects live in a server process. That is almost correct. In reality, the objects live in a container called a POA (the term stands for Portable Object Adapter, but the origin of the name is not particularly interesting), and the POAs, in turn, live in a server process. The motivation for this is that different POAs can provide different qualities of service (or policies in CORBA terminology). For example, one POA might be single-threaded while another POA might be multi-threaded. The quality of service provided by a POA is applied to the servants within that POA. So by putting some servants in one

Page 35: Developing a CORBA Server

POA and other servants in another POA, a single server process can host objects with a variety of different qualities of service: some objects might be single-threaded, while others are multi-threaded; some objects might be transient (meaning temporary), while other objects might be persistent (meaning that a client application can continue communicating with the object even if the server processes crashes and is restarted). CORBA provides an API that enables server developers to create multiple POAs, each with a potentially different quality of service. The CORBA runtime system in a server pre-creates a “rootPOA” that is multi-threaded and transient. Since that quality of service is suitable for the needs of our demonstration server, we do not need to explicitly create a POA.

A water tap (or faucet as the Americans call

Page 36: Developing a CORBA Server

it) is used for turning on and off the flow of water. A POA manager is similar to a water tap except that it controls the flow of incoming requests. When a CORBA server starts, the POA manager associated with the root POA is in a holding state, which means if any requests from clients arrive then they will be queued up. This holding state enables a server process to complete its initialization without having to worry about processing incoming requests. When the server has completed its initialization, it puts its POA manager(s) into the active state, so that incoming requests can be dispatched, via POAs, to servants.

Collapse | Copy Code

#include <iostream>

#include "OB/CORBA.h"

#include <OB/Cosnaming.h>

#include "crypt.h"

#include "cryptimpl.h"

Page 37: Developing a CORBA Server

using namespace std;

int main(int argc, char** argv)

{

// Declare ORB and servant object

CORBA::ORB_var orb;

CryptographicImpl* CrypImpl = NULL;

try {

// Initialize the ORB.

1 orb = CORBA::ORB_init(argc, argv);

// Get a reference to the root POA

2

CORBA::Object_var rootPOAObj =

orb->resolve_initial_references("RootPOA");

// Narrow it to the

Page 38: Developing a CORBA Server

correct type

PortableServer::POA_var rootPOA =

PortableServer::POA::_narrow(rootPOAObj.in());

// Create POA policies

CORBA::PolicyList policies;

policies.length(1);

policies[0] =

rootPOA->create_thread_policy

(PortableServer::SINGLE_THREAD_MODEL);

// Get the POA manager object

Page 39: Developing a CORBA Server

PortableServer::POAManager_var manager = rootPOA->the_POAManager();

3 // Create a new POA with specified policies

PortableServer::POA_var myPOA = rootPOA->create_POA

("myPOA", manager, policies);

// Free policies

CORBA::ULong len = policies.length();

for (CORBA::ULong i = 0; i < len; i++)

policies[i]->destroy();

// Get a reference to the Naming Service root_context

4

Page 40: Developing a CORBA Server

CORBA::Object_var rootContextObj =

orb->resolve_initial_references("NameService");

// Narrow to the correct type

CosNaming::NamingContext_var nc =

CosNaming::NamingContext::_narrow(rootContextObj.in());

// Create a reference to the servant

5CrypImpl =

new CryptographicImpl(orb);

// Activate object

PortableServer::ObjectId_var myObjID =

myPOA->activate_object(CrypI

Page 41: Developing a CORBA Server

mpl);

// Get a CORBA reference with the POA through the servant

6

CORBA::Object_var o = myPOA->servant_to_reference(CrypImpl);

// The reference is converted to a character string

CORBA::String_var s = orb->object_to_string(o);

7 cout << "The IOR of the object is: " << s.in() << endl;

CosNaming::Name name;

name.length(1);

name[0].id = (const char *) "CryptographicService";

name[0].kind

Page 42: Developing a CORBA Server

= (const char *) "";

// Bind the object into the name service

8 nc->rebind(name,o);

// Activate the POA

manager->activate();

cout << "The server is ready.

Awaiting for incoming requests..." << endl;

// Start the ORB

9 orb->run();

} catch(const CORBA::Exception& e) {

// Handles CORBA exceptions

cerr << e << endl;

}

Page 43: Developing a CORBA Server

// Decrement reference count

if (CrypImpl)

10CrypImpl-

>_remove_ref();

// End CORBA

if (!CORBA::is_nil(orb)){

try{

11 orb->destroy();

cout << "Ending CORBA..." << endl;

} catch (const CORBA::Exception& e)

{

cout << "orb->destroy() failed:" << e << endl;

return 1;

}

}

return 0;

}

Initializes the ORB.

Get the root POA. Later on, we will create a servant object and activate

Page 44: Developing a CORBA Server

(that is, insert) it into this POA.

Access the root POA’s POA manager. Initially this POA manager is in the holding state, so any incoming requests are queued up. When the server’s initialization is complete, we will put this POA manager into the active state so that incoming requests can be dispatched.

Obtain a reference for the naming service so that after we create a servant we can export its object reference to the naming service.

A CryptographicImpl servant object is dynamically created.

Calling servant_to_reference() provides us with a object reference for the servant.

The reference is converted to a character string in order to display it.

The rebind() operation is used to register the servant’s object reference in the naming service.

The ORB enters an

Page 45: Developing a CORBA Server

event loop, so it can await incoming requests.

Servants are reference counted. When a servant is created, its reference count is initialized to 1. Now that we are finished with the servant, we decrement its reference count so the CORBA runtime system knows it can be deleted safely.

Destroy the ORB.

2.6 Implementing the Client

The client application performs some CORBA initialization steps, retrieves the server’s object reference from the naming service and then goes into a loop, invoking encrypt() and decrypt() on it. When the client is finished, it invokes shutdown() to ask the server to terminate.

Collapse | Copy Code

#include <iostream>

#include <string>

#include

Page 46: Developing a CORBA Server

"OB/CORBA.h"

#include "OB/Cosnaming.h"

#include "crypt.h"

using namespace std;

int main(int argc, char** argv)

{

// Declare ORB

CORBA::ORB_var orb;

try {

// Initialize the ORB

1 orb = CORBA::ORB_init(argc, argv);

// Get a reference to the Naming Service

2

CORBA::Object_var rootContextObj =

Page 47: Developing a CORBA Server

orb->resolve_initial_references("NameService");

CosNaming::NamingContext_var nc =

CosNaming::NamingContext::_narrow(rootContextObj.in());

CosNaming::Name name;

name.length(1);

name[0].id = (const char *) "CryptographicService";

name[0].kind = (const char *) "";

// Invoke the root context to retrieve the object reference

3

CORBA::Object_var managerObj = nc->resolve(name);

//

Page 48: Developing a CORBA Server

Narrow the previous object to obtain the correct type

::CaesarAlgorithm_var manager =

4::CaesarAlgori

thm::_narrow(managerObj.in());

string info_in,exit,dummy;

CORBA::String_var info_out;

::CaesarAlgorithm::charsequence_var inseq;

unsigned long key,shift;

try{

do{

cout << "\nCryptographic service client" << endl;

cout << "----------------------------

Page 49: Developing a CORBA Server

" << endl;

do{ // Get the cryptographic key

if (cin.fail())

{

cin.clear();

cin >> dummy;

}

cout << "Enter encryption key: ";

cin >> key;

} while (cin.fail());

Page 50: Developing a CORBA Server

do{ // Get the shift

if (cin.fail())

{

cin.clear();

cin >> dummy;

}

cout << "Enter a shift: ";

cin >> shift;

} while (cin.fail());

// Used for debug pourposes

//key

Page 51: Developing a CORBA Server

= 9876453;

//shift = 938372;

getline(cin,dummy); // Get the text to encrypt

cout << "Enter a plain text to encrypt: ";

getline(cin,info_in);

// Invoke first remote method

5inseq

= manager->encrypt

(info_in.c_str(),key,shift);

cout << "------------------------------------------"

Page 52: Developing a CORBA Server

<< endl;

cout << "Encrypted text is: "

<< inseq->get_buffer() << endl;

// Invoke second remote method

6

info_out = manager->decrypt(inseq.in(),key,shift);

cout << "Decrypted text is: "

<< info_out.in() << endl;

cout << "-------------------------------------------"

<< endl;

cout << "Exit? (y/n): ";

Page 53: Developing a CORBA Server

cin >> exit;

} while (exit!="y");

// Shutdown server message

7manager-

>shutdown();

} catch(const std::exception& std_e){

cerr << std_e.what() << endl;

}

}catch(const CORBA::Exception& e) {

// Handles CORBA exceptions

cerr << e << endl;

}

// End CORBA

if (!CORBA::is_nil(orb)){

Page 54: Developing a CORBA Server

try{

orb->destroy();

cout << "Ending CORBA..." << endl;

} catch(const CORBA::Exception& e)

{

cout << "orb->destroy failed:" << e << endl;

return 1;

}

}

return 0;

}

Initialize the ORB.

Obtain a reference for the naming service.

Call resolve() (which means lookup) on the naming service to retrieve the server’s object reference.

The narrow() operation is, in essence, a typecast. We have to narrow() the object reference

Page 55: Developing a CORBA Server

received from the naming service into the correct subtype so we can invoke operations on it.

Make a remote invocation of the encrypt() operation.

Make a remote invocation of the decrypt() operation.

Make a remote invocation of the shutdown() operation.

2.7 Running the Client-server Application

Once we have implemented the client and the server, it’s time to connect them. Because our demonstration client and server exchange object references via the naming service, we must ensure that the naming service (which is called nameserv in Orbacus) is running. We use some command-line options to tell the naming service the host and port on which it should listen.

Collapse | Copy Code

nameserv -OAhost localhost -OAport

Page 56: Developing a CORBA Server

8140

After this, we can start the server with a command-line option to tell it how to contact the naming service.

Collapse | Copy Code

server -ORBInitRef NameService=corbaloc:iiop:localhost:8140/NameService

Finally we can start the client, again with a command-line option to tell it how to contact the naming service.

Collapse | Copy Code

client -ORBInitRef NameService=corbaloc:iiop:localhost:8140/NameService

Page 57: Developing a CORBA Server

3 CORBA Benefits and Drawbacks

CORBA offers several benefits.

First, implementations of CORBA are available for many programming languages (such as C, C++, Java, COBOL, Ada, SmallTalk and Python, Tcl and Perl) and many operating systems (including Windows, UNIX, mainframes and embedded devices). Not all client-server technologies can claim this. For example, Microsoft technologies (COM, DCOM and .NET) have traditionally been available only on Windows. Java RMI works across multiple operating systems,

Page 58: Developing a CORBA Server

but it can be used only in Java-based applications.

Second, when making a remote call, CORBA marshals parameters into a compact binary format for transmission across the network. This compact format saves on network bandwidth. In addition, the CPU overhead required to marshal parameters into the binary format is also quite low. In contrast, some newer client-server technologies, such as SOAP, marshal parameters into XML format when making remote calls. XML is very verbose so it uses a lot of bandwidth; usually at least ten times more bandwidth than an equivalent CORBA call. In addition, when a SOAP-based server receives a request, it must parse the XML to extract the parameter data. Doing this requires a lot of CPU time. In contrast, parameters can be marshaled in and out of CORBA’s binary message format much

Page 59: Developing a CORBA Server

more quickly.

Third, CORBA has been around for well over a decade, which means that its technology is quite stable and feature rich.

Of course, CORBA is not perfect. Its main drawback is that the power and flexibility of CORBA comes with a relatively steep learning curve. An open-source library called the CORBA Utilities eases some, but not all, of the learning curve for C++ and Java developers.

Bottom of Form