|
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.
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:
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:
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:
IDL Interface type name |
Protocol and address detail |
Object key | ||||
Interface repository identifier | IIOP | Host domain name | Port number | Adapter name | Object name |
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.
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:
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.
module Computer { interface Mouse {}; interface Keyboard {}; };
As seen before, an IDL interface describes the available methods in the CORBA object that implements that interface through the skeleton.
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:
oneway void docall(in long data);
The optional expression raises
indicates user defined exceptions that can be thrown to finish the method execution.
interface Network { Computer getHost(in long host) raises (NetworkException); }; exception NetworkException {string error_code}
Interfaces can have multiple inheritance. For example:
interface A {}; interface B : A{}; interface C{}; interface Z : B, C{};
sequence
: Defines a type to a variable length sequence. The size can be fixed or undefined.
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.
typedef string <8> Computername; string Computername;
array
: Defines a type for a multidimensional sequence of fixed size of elements.
typedef long ports[12]; typedef Network Matrix[10][8];
record
: Defines a type containing a set of related entities. Very similar to C/C++.
struct information{ string type; Computer cluster[200]; long size; };
enumerated
: Relates a name type with a small set of integer values.
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.
union Exp switch(Platform) case PC: string motherboard; case MAC: string osversion; case IBM: string provider; case WorkStation: string multiprocessor_number; };
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 |
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.
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.
attribute string number_of_keys;
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.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.
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:
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:
// 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.
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.
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:
#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.
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:
#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; }
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.CryptographicImpl
object is dynamically created.servant_to_reference
is used to obtain a CORBA reference from a servant.CaesarAlgorithm
interface reference.rebind
.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:
#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; }
CaesarAlgorithm
interface from the CORBA object reference.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:
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:
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:
Finally, we can proceed to call the client by prompting the following:
client -ORBInitRef NameService=corbaloc:iiop:localhost:8140/NameService
and we will get a client application running and connecting with the server through the Internet:
Advantages:
Disadvantages: