A simple C++ client/server in CORBA

10 votes for this Article.
Popularity: 3.64 Rating: 3.64 out of 5
2 votes, 20.0%
1
2 votes, 20.0%
2
1 vote, 10.0%
3
1 vote, 10.0%
4
4 votes, 40.0%
5

Index

1. Introduction

1.1 A brief overview of CORBA

CORBA is a specification of OMG (Object Management Group) formed in 1989, with the intention of adopting distributed object systems to use the advantages of object-oriented programming to develop distributed software for distributed systems. To achieve this goal, the OMG advocated the use of open systems based on standard object-oriented interfaces. The advantage of this issue was any distributed object could be implemented in any programming language and could communicate with one another. Therefore, applications could work in heterogeneous hardware, networks, operating systems, and programming languages. The OMG introduced the ORB (Object Request Broker) whose role is to help a client to invoke a method on an object transparently.

In 1991, a group of companies created a new specification for ORB called CORBA, which stands for Common Object Request Architecture. The CORBA 2.0 specification was defined in 1996, which allows implementations made by different developers to communicate with one another. This standard, called General Inter-ORB protocol or GIOP, that was intended to be implemented over any transport layer using TCP/IP, is called Internet Inter-ORB protocol or IIOP.

1.2 CORBA architecture

The architecture was designed to support the ORB that allows clients to invoke remote methods in other objects, where the clients and servers can be implemented in a variety of programming languages such as: Ada, C, C++, Java, SmallTalk, and Python. The main layout of the CORBA architecture is shown in the image below:

arch.gif

There are two kinds of invocations in CORBA: static and dynamic. Static invocations are used when the remote interface of the remote object is known at compile time, so the stub for the client and the skeleton can be used. However, if the remote interface for the object is not known at compile time, a dynamic invocation must be used. Most programmers use static invocation because it is a more natural and smart programming model.

The elements shown above are:

  • ORB Core: It's the base of the CORBA architecture, it enables to communicate through a request / reply protocol between the server and the client side. Requests and responses between objects are delivered in a standard format defined by the Internet Inter-ORB Protocol (IIOP).
  • Portable Object Adapter (POA): It has several roles like: creating remote object references for CORBA objects, dispatching RMI via a skeleton to the suitable servant, and activating objects. An object adapter gives each CORBA object a unique name, which forms part of its remote object reference. Each object adapter has its own name, which also forms part of the remote object references of all the CORBA objects it manages. The CORBA 2.2 standard for object adapters is called the Portable Object Adapter. It is called portable because it allows applications and servants to be run in any ORB produced by different developers.
  • Skeleton: The skeleton classes are generated in the server language by the IDL compiler. The remote method invocation (RMI) are dispatched through the skeleton for the suitable servant, and unmarshals arguments in the request messages and marshals exceptions and results in the reply messages.
  • Client stub/proxies: Are implemented in the client language and generated with the IDL compiler. As before, the client stub marshals the arguments in the invocation request and unmarshals exceptions and results in the replies.
  • Implementation repository (IMR): Handles the automated start and restart of servers and realizes object persistency.
  • Interface repository (IFR): The role of the IFR is to provide information about registered IDL interfaces to clients and servers that require it. It can provide the name of the methods, and for each method, its argument names and exceptions.
  • Dynamic Skeleton Interface (DSI): This allows a CORBA object to accept invocations on an interface for which it has no skeleton because the type of its interface was not known at compile time.
  • Dynamic Interface Invocation (DII): In some applications, a client without a proxy class needs to invoke a method in a remote object. It is used when it is not possible to use stubs. The client can obtain the required information about the available remote methods in the CORBA object through the Interface repository (IFR).

1.3 Reference to remote objects

CORBA 2.0 specifies a format for remote object reference. The references that use this format are called interoperable remote object references (IOR, interoperable object reference). The next block diagram shows the IOR details:

IOR format:

IDL Interface type name

Protocol and address detail

Object key
Interface repository identifier IIOP Host domain name Port number Adapter name Object name
  • The first field specifies the type name of the IDL interface of the CORBA object. If the ORB has an interface repository, this type name is also the IDL interface identifier in the interface repository.
  • The second field specifies the transport protocol and the required details in order that the transport protocol identifies the server. The Internet Inter-ORB protocol (IIOP) uses TCP/IP, in which the server address contains the host domain name and a port number.
  • The third field is used for the ORB to identify a CORBA object. It consists of the object adapter in the server and the object name of a CORBA object specified by the object adapter.

There are two kinds of IORs: Transients and Persistents. Transient IORs last only as long as the process that host them, whereas Persistent IORs last between activations of the CORBA objects. A transient IOR contains the address details of the server containing the CORBA object, whereas a persistent IOR will contain the implementation repository addresses.

1.4 IDL basics

The IDL (Interface Definition Language) is designed to allow objects implemented in other languages to invoke one another. The IDL uses a notation to define interfaces with methods and input and output parameters. The IDL is a neutral language, and is later converted to a skeleton and stub by the IDL compiler, usually typed as idl (in Orbacus ORB) or idl2cpp (in VisiBroker and Orbit), for example. This allows a Java client to communicate with C++ server methods. The IDL language grammar is very similar to C++, and is a subset of C++ ANSI. The IDL provides facilities to define the follwing elements:

1.4.1 Modules

The module construct allows interfaces and other IDL type definitions to be grouped in logical units. The keyword is module, and avoids name collisions of others outside it.

Collapse Copy Code
   module Computer {

interface Mouse {};

interface Keyboard {};

};

1.4.2 Interfaces and their methods

As seen before, an IDL interface describes the available methods in the CORBA object that implements that interface through the skeleton.

Collapse Copy Code
   module Computer {

interface Mouse {

void setCursor(in long x,in long y);

void getCursor(out long x,out long y);

};

interface Keyboard {};

};

The IDL interface methods are specified with the keywords in, out, or inout depending on if the arguments are for input, output, or both. A method specified as oneway indicates that the client that invokes the method will not be locked while the target object performs the method. For example:

Collapse Copy Code
oneway void docall(in long data);

The optional expression raises indicates user defined exceptions that can be thrown to finish the method execution.

Collapse Copy Code
   interface Network {

Computer getHost(in long host) raises (NetworkException);

};

exception NetworkException {string error_code}

Interfaces can have multiple inheritance. For example:

Collapse Copy Code
   interface A {};

interface B : A{};

interface C{};

interface Z : B, C{};

1.4.3 Constructed types

  • sequence: Defines a type to a variable length sequence. The size can be fixed or undefined.
    Collapse Copy Code
    typedef sequence<Computer,100> Network;
    
        typedef sequence<Computer> Network;
  • string: Defines a character sequence, null terminated. Can be unlimited or fixed. Similar to an STL string.
    Collapse Copy Code
    typedef string <8> Computername;
    
        string Computername;
  • array: Defines a type for a multidimensional sequence of fixed size of elements.
    Collapse Copy Code
    typedef long ports[12];
    
        typedef Network Matrix[10][8];
  • record: Defines a type containing a set of related entities. Very similar to C/C++.
    Collapse Copy Code
    struct information{
    
        string type;
    
        Computer cluster[200];
    
        long size;
    
        };
  • enumerated: Relates a name type with a small set of integer values.
    Collapse Copy Code
    enum Platform (PC, MAC, IBM, WorkStation);
  • union: The IDL discriminated union allows one of a given set of types to be passed as an argument.
    Collapse Copy Code
    union Exp switch(Platform)
    
        case PC: string motherboard;
    
        case MAC: string osversion;
    
        case IBM: string provider;
    
        case WorkStation: string multiprocessor_number;
    
        };

1.4.4 Primitive types

Type Signification
short 16 bit signed integer
unsigned short 16 bit unsigned integer
long 32 bit signed integer
unsigned long 32 bit unsigned integer
long long 64 bit signed integer
unsigned long long 64 bit unsigned integer
float 32 bit IEEE float
double 64 bit IEEE float
long double 128 bit float
boolean boolean value: TRUE or FALSE
octet 8 bit byte
char 8 bit character (ISO latin-1)
wchar international character format
string character string based on char
wstring character string based on wchar

1.4.5 Type any

The type any is an universal container. It can be used when we don't know at compile time what IDL types you will need to transmit between the client and the server. The any type can represent any of the primitive and constructed types previously seen.

1.4.6 Attributes

IDL interfaces can have as many attributes as methods. The attributes can be defined as readonly. The attributes are private to CORBA objects, but two access methods are generated for each one automatically by the IDL compiler, i.e, get and set. The readonly attributes have only the get method.

Collapse Copy Code
attribute string number_of_keys;

1.4.7 Parameter passing

  • CORBA objects: Any parameter of an interface type is passed by reference. All CORBA objects that implements the interface type inherits the Object base type which represents a reference to a remote object.
  • Primitive and constructed types: They are copied and passed by value.

1.5 CORBA basic services

  • Naming service: It allows names to be bound to remote object references of CORBA objects within naming contexts. A naming context is the scope within a set of names - each name must be unique. A name can be associated with either an object reference for a CORBA object in an application or with another context in the naming service. Contexts can be nested to provide a hierarchical name space.
  • Event service and notification service: The CORBA event services define interfaces, allowing objects of interest, called suppliers, to communicate notifications to subscribers, called consumers, in a push and pull way. The notification can be pushed by the suppliers to the consumers as pulled by the consumer from the supplier.
  • Security service: It allows authentication of servers and users, and controls access to CORBA objects when an RMI is produced. The access rights can be defined with ACLs, etc.
  • Trading service: In contrast to the Naming service which allows CORBA objects to be located by name, the Trading service allows to locate them by attributes like a directory service.
  • Transaction service and concurrency control service: The transaction service allows distributed CORBA objects to participate in either flat or nested transactions. The concurrency service allows concurrency access to CORBA objects.
  • Persistent object service: A CORBA object can be stored in a passive way, while they are not in use, and then can be recovered and activated on demand. The Persistent object service provides a persistent object store of CORBA objects.

2. Developing a simple client / server

For the development of a basic client/server in CORBA, I have used the Orbacus ORB that you can find here. It's a free ORB for non-commercial purposes, with an unlimited time of use. The package is available for both Java and C++, and the specification is up-to-date CORBA compliant.

2.1 The business logic domain

The demo application is inspired by a cryptographic service where the client makes a request for encrypting and decrypting a plaint text to a server which performs the operations. The encryption algorithm is based on the Caesar cipher, which is one of the simplest encryption techniques. It's a type of substitution cipher in which each letter in the plain text is replaced by a letter, some fixed number of positions down the alphabet. Besides this technique, I added a XOR operation with an encryption key after the shift operation to obscure the relationship. In the image below, you can see this technique:

caesar.png

Caesar cipher

2.2 Writing the IDL

The first step before implementing the C++ application is to write the IDL file with the specific syntax defining the interfaces, structures, sequences, and methods. The IDL file created for the demo application is the following:

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

};

There is only one interface that will represent the remote CORBA object which will perform the encryption operations. This interface defines an ANSI charsequence and two methods.

2.3 Generating the skeleton and the stub

After writing the IDL file, we will proceed to compile it by calling idl.exe crypt.idl. This little compiler will generate the skeleton and the stub, with the following names:

Skeleton Stub
crypt_skel.h crypt.h
crypt_skel.cpp crypt.cpp

Note: Try to configure your system environment variables to the Orbacus installation package, for libraries and binaries.

2.4 Implementing the sevant class

The next step is to implement the servant class. For this purpose, we will need to create a class for the servant that inherits from the skeleton class, as shown below:

Collapse Copy Code
#include "OB/CORBA.h"

#include "crypt_skel.h"

#include <iostream>

#include <string>

class CryptographicImpl : virtual public ::POA_CaesarAlgorithm,

virtual public PortableServer::RefCountServantBase

{

public:

CryptographicImpl() {}

// Caesar text encryption algorithm

::CaesarAlgorithm::charsequence* encrypt(const char* info,

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

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)

{

*i+= shift;

*i ^= k;

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

}

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

return outseq;

}

// Caesar text decryption algorithm

char* decrypt(const ::CaesarAlgorithm::charsequence& info,

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

throw(::CORBA::SystemException)

{

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;

}

};

The servant implements the CORBA object methods functionality. The servant is called by the skeleton when a client calls a CORBA object method implemented by the servant. The Orbacus IDL compiler generates an empty implementation of the servant, called cryptimpl.h.

2.5 Creating the server

Before ending the server side implementation, it's necessary to create an entry point that instantiates and activates the servant class. We will have to implement a server with the CORBA initialization. The following code explains this issue:

Collapse Copy Code
#include <iostream>

#include "OB/CORBA.h"

#include <OB/Cosnaming.h>

#include "crypt.h"

#include "cryptimpl.h"

using namespace std;

int main(int argc, char** argv)

{

try {

// Initialize the ORB.

1       CORBA::ORB_var 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 correct type

PortableServer::POA_var rootPOA =

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

// Create POA policies

CORBA::PolicyList policies;

policies.length(1);

3       policies[0] =

rootPOA->create_thread_policy(PortableServer::SINGLE_THREAD_MODEL);

// Get the POA manager object

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

// Create a new POA with specified policies

PortableServer::POA_var myPOA =

rootPOA->create_POA("myPOA", manager, policies);

// Get a reference to the Naming Service root_context

5       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

6       CryptographicImpl* CrypImpl = new CryptographicImpl();

// Activate object

PortableServer::ObjectId_var myObjID = myPOA->activate_object(CrypImpl);

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

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

// Get a reference to the CaesarAlgorithm interface

8       CaesarAlgorithm_var cav = CaesarAlgorithm::_narrow(o);

// The reference is converted to a character string

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

cout << "The IOR of the object is: " << s << endl;

CosNaming::Name name;

name.length(1);

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

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

// Bind the object into the name service

10      nc->rebind(name,o);

// Activate the POA

manager->activate();

cout << "The server is ready. Awaiting for incoming requests..."

<< endl;

// Start the ORB

11      orb -> run();

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

// Handles CORBA exceptions

cerr << e << endl;

return 1;

}

return 0;

}
  1. Initializes the CORBA ORB.
  2. Gets the root POA. The server object must be registered using an object adapter. In this case, we register the object in the root POA. In most cases, this object adapter is enough.
  3. Set the POA policies to SINGLE_THREAD_MODEL. This POA policy manages one request at a time, instead of multithreading, so we get it multithread-aware. If you want to use multithread capabilities, read a CORBA book about JTCThreads. This approach is safe and clear, but has bad performance.
  4. A POA Manager is obtained. The Manager controls a set of object adapters, allowing them to work.
  5. A reference to the Name Service is obtained. It will allow to register an object reference in a naming context.
  6. A servant CryptographicImpl object is dynamically created.
  7. The POA method servant_to_reference is used to obtain a CORBA reference from a servant.
  8. The reference is converted to a CaesarAlgorithm interface reference.
  9. The reference is converted to a character string in order to display it.
  10. The CORBA reference from a servant is registered in the Name Service using a naming context, through a call to rebind.
  11. Finally, the ORB starts and keeps awaiting requests.

2.6 Implementing the client

The client side is a program that allows to communicate with the remote CORBA object instantiated in a server. The following code is a sample of a client invoking the remote object:

Collapse Copy Code
#include <iostream>

#include <string>

#include "OB/CORBA.h"

#include "OB/Cosnaming.h"

#include "crypt.h" // Include the stub

using namespace std;

int main(int argc, char** argv)

{

try {

// Initialize the ORB

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

// Get a reference to the Naming Service

2       CORBA::Object_var rootContextObj =

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

// Narrow the previous object to obtain the correct type

4       ::CaesarAlgorithm_ptr manager =

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

string info_in,info_out,exit,dummy;

::CaesarAlgorithm::charsequence* inseq;

unsigned long key,shift;

try{

do{

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

cout << "----------------------------" << endl;

do{ // Get the cryptographic key

if (cin.fail())

{

cin.clear();

cin >> dummy;

}

cout << "Enter encryption key: ";

cin >> key;

} while (cin.fail());

do{ // Get the shift

if (cin.fail())

{

cin.clear();

cin >> dummy;

}

cout << "Enter a shift: ";

cin >> shift;

} while (cin.fail());

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

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

getline(cin,info_in);

// Invoke first remote method

5              inseq = manager->encrypt(info_in.c_str(),key,shift);

cout << "----------------------------------------------" << endl;

cout << "Encrypted text is: " << inseq->get_buffer() << endl;

// Invoke second remote method

6              info_out = manager->decrypt(*inseq,key,shift);

cout << "Decrypted text is: " << info_out << endl;

cout << "----------------------------------------------" << endl;

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

cin >> exit;

} while (exit!="y");

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

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

return 1;

}

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

// Handles CORBA exceptions

cerr << e << endl;

return 1;

}

return 0;

}
  1. Initialize the CORBA ORB.
  2. Get a reference to the Name Service object.
  3. Invoke the root context to retrieve the object reference.
  4. Get a pointer to the CaesarAlgorithm interface from the CORBA object reference.
  5. Invoke the first remote method, passing three input arguments, and return a CORBA sequence.
  6. Invoke the second remote method, passing the previous returned sequence by value.

2.7 Working all together

Once we have implemented the client and the server, it's time to connect them. Let's start with the server. Previously, to run it, we had to call the Orbacus Name Service application by searching for nameserv.exe. Therefore, the call must be done in this form:

Collapse Copy Code
nameserv -OAhost localhost -OAport 8140

Instead of using the localhost, we can use an external LAN or Internet IP address by replacing localhost with the IP.

After this, we can execute the server with these parameters:

Collapse Copy Code
server -ORBInitRef NameService=corbaloc:iiop:localhost:8140/NameService

It's also possible to replace localhost with an external IP.

We will get the following result:

server.gif

Finally, we can proceed to call the client by prompting the following:

Collapse Copy Code
client -ORBInitRef NameService=corbaloc:iiop:localhost:8140/NameService

and we will get a client application running and connecting with the server through the Internet:

client.gif

3. CORBA advantages & disadvantages

Advantages:

  • Multiple OS support and platforms.
  • Multiple language support.
  • Great variety of services: messaging, events, transactions, persistence, concurrence, etc.
  • Managed by the OMG and what is the same: standard compliance.

Disadvantages:

  • Complexity: Programming CORBA application is hard and tedious. The learning curve is too steep.
  • Bureaucracy: The evolution of CORBA specifications requires many steps of bureaucracy, so the platform evolution is slow.
  • Few free solutions: As a consequence of its complexity and the slow evolution.

4. References

  • Distributed Systems. Concepts and Design (Third edition). George Coulouris, Jean Dollimore, Tim Kindberg.
  • Advanced CORBA programming with C++ (First edition). Michi Henning and Steve Vinoski.
  • Notes of the Distributed System subject from UNED - National University of Distance Education.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPL)

About the Author

Carlos Jiménez de Parga



Occupation: Software Developer
Location: Spain Spain

Other popular Internet / Network articles:

你可能感兴趣的:(server)