CORBA Program Development, Part 2

This month, the more advanced techniques of naming and event services are discussed.
Push Method

In the Push Method, a Supplier will connect to the Event Channel and initiate a push of an event onto the Event Channel whenever it is ready to do so. It is the Event Channel's responsibility to buffer those events until they are delivered to one or more interested Consumers. In the Push Model, it is the Supplier that initiates the flow of events to the Event Channel. When a Supplier wants to connect to an Event Channel, it needs an object within the Event Channel to “pretend” it is a Consumer. This allows the Supplier to simply deliver events to its “Consumer”, when in reality, its Consumer is simply a proxy for the actual Consumer, which is outside the Event Channel. It is to this proxy “Consumer” that the Push Supplier pushes events. Thus, the Proxy object is not a real Consumer, but merely an object within the Event Channel that provides a delivery mechanism through which the Supplier can deliver messages.

A Push Consumer will likewise connect to a “proxy” object, a proxy that represents the Push Supplier. When the Event Channel has a message available, the Push Supplier proxy will deliver (push) the message to the actual Consumer object. The message path is from the actual Push Supplier, through its Proxy Push Consumer, to the Proxy Push Supplier and finally to the Push Consumer itself. There are other variations of this, as our later example will show.

Pull Method

In the Pull Method, the Event Channel will pull data from the Supplier. In the Pull Model, it is the Consumer that drives the delivery of messages. A Pull Supplier will connect to a Proxy Pull Consumer. Again, as far as the Pull Supplier is concerned, it can consider this proxy object as a real Consumer that will request events periodically. An interested Pull Consumer object will then connect on the other end of the Event Channel to a Proxy Pull Supplier. When a Pull Consumer is ready to receive an event, it will initiate either a pull or try_pull call on its Proxy Pull Supplier, which will in turn query the Proxy Pull Consumer connected to the actual Pull Supplier, to request another event be delivered. In this way, the Consumer drives the data, when it is ready to process another message. Some implementations of the Pull Method will allow the Proxy Pull Supplier to periodically pull events from the Supplier at regular intervals in an attempt to keep a buffer full of events for consumers when they request delivery.

The nice thing about the Event Channel abstraction is a communication does not need to be either entirely Push Model or Pull Model. A Push Supplier may indirectly connect to one or more Pull Consumers, and several Pull Suppliers may connect to one or more Push Consumers. It is the Event Channel logic that allows such interrelationships disproportionality among objects. It is the application design that drives the decisions concerning suppliers, consumers and their numbers.

Regardless of the relationship among suppliers and consumers, to establish a connection and deliver events through the Event Channel the following five steps must be taken:

  • The client (Supplier or Consumer) must bind to the Event Channel, which must already have been created by someone, perhaps the client.

  • The client must get an Admin object from the Event Channel.

  • The client must obtain a proxy object from the Admin object—a Consumer Proxy for a Supplier client and a Supplier Proxy for a Consumer client.

  • Add the Supplier or Consumer to the event channel via a connect call.

  • Transfer data between the client and the Event Channel via a push, pull or try_pull call.

When messages are delivered through the Event Channel, they can be either “typed” or “untyped”. Typed messages are those defined in an IDL which are type-checked at compile time. Untyped events, the most common, adhere to the standard Event Services interfaces and are packaged as type CORBA::Any, which is a wrapper around all known CORBA types. It is this “Any” type that is actually sent from a Supplier Object to a Consumer Object. The Supplier will construct an Any, and the Consumer, upon receipt of the message, will derive the true value from the Any wrapper. This allows for great flexibility in delivering messages, as a Supplier may pass a string first, a long value second and an array third, all through packaging the values into an Any. The example code shows how to create, embed and extract values from Any types.

Our example incorporates both the Naming Service lookup as well as an implementation of a Supplier and a Consumer interacting through the use of the Event Service. The Supplier implements the Push Supplier Model and the Consumer implements the Pull Consumer Model, thus illustrating that the models do not have to be all of one type. Listing 1 shows Consumer.C, and Listing 2 shows Supplier.C. These listings are available by anonymous download in the file ftp://ftp.linuxjournal.com/pub/lj/listings/issue62/3213.tgz.

The first step the Consumer must take is to find the root naming context. This is accomplished by calling resolve_initial_references and then narrowing the returned IOR. The resulting object is the root naming context we can then use to resolve our Event Service.

CORBA::Object_var nsobj =
orb->resolve_initial_references("NameService");
assert(! CORBA::is_nil(nsobj));
CosNaming::NamingContext_var context =
CosNaming::NamingContext::_narrow(nsobj);
assert(! CORBA::is_nil(context));

As we turn to the Event Service sections of the code, we notice that the first thing the Consumer does after obtaining the initial context from the Naming Service is resolve and narrow the EventChannelFactory.

CosNaming::Name name;
name.length(1);
name[0].id =
CORBA::string_dup("EventChannelFactory");
name[0].kind = CORBA::string_dup("factory");
CORBA::Object_var obj;
obj = context->resolve(name);
MICO uses the factory referenced above as a generic CORBA::Object to create a new Event Channel object by first narrowing the generic reference, then calling the factory's create_eventchannel function:
SimpleEventChannelAdmin::EventChannelFactory_var
        factory;
CosEventChannelAdmin::EventChannel_var event_channel;
factory =
SimpleEventChannelAdmin::EventChannelFactory::_narrow(obj);
event_channel = factory->create_eventchannel();
We then use the Naming Service to bind this newly created Event Channel object to the name TestEventChannel via the Naming Service's bind method. This is done so that the Supplier will be able to locate this particular Event Channel by the name TestEventChannel when needed.
name.length(1);
name[0].id =
CORBA::string_dup("TestEventChannel");
name[0].kind = CORBA::string_dup("");
context->bind(name,<\n>
CosEventChannelAdmin::EventChannel::
_duplicate(event_channel));
Once the Event Channel has been created and named, the Event Channel object (event_channel) is used to obtain a reference to a ConsumerAdmin object through the for_consumers function. The ConsumerAdmin object provides the proxies for the Consumer clients of the Event Channel. It allows the Consumer to obtain the appropriate Supplier Proxy. In our case, we use the ConsumerAdmin object to provide us (a Pull Consumer) with a proxy Pull Supplier. This allows our Consumer object to act as if it were communicating directly with a Supplier that expects us to be “pulling” events from it. Of course, that's not actually the case. Our Supplier is really a Push Supplier that pushes events onto the Event Channel. The proxies decouple the Consumer and Supplier objects and allow them to function as if they were directly connected, when in fact, their connection is indirect. Once we have the ConsumerAdmin, we use it to create our Push Consumer proxy:
CosEventChannelAdmin::ConsumerAdmin_var
Consumer_admin;
Consumer_admin = event_channel->for_consumers();
...
CosEventChannelAdmin::ProxyPullSupplier_var
proxy_Supplier;
proxy_Supplier =
Consumer_admin->obtain_pull_Supplier();
Once the Consumer has obtained a reference to its Supplier Proxy, it then notifies the Event Channel of its interest in receiving events from it through a call to the Proxy's connect_pull_Consumer method. An implementation of the Event Service's Pull Consumer interface is passed into the proxy_Supplier to make the connection.
proxy_Supplier->connect_pull_Consumer
(CosEventComm::PullConsumer::_duplicate(Consumer));
Once connected, calls can be made on the Proxy Pull Supplier's pull or try_pull functions. The PullSupplier interface is:
interface PullSupplier
  {
    any pull() raises(Disconnected);
    any try_pull(out boolean has_event)
         raises(Disconnected);
    void disconnect_pull_Supplier();
  };
In our case, we have the Consumer spawn a worker thread, and we pass the Pull Supplier Proxy reference to that thread, the one that actually makes the try_pull call. The try_pull call is an asynchronous polling mechanism allowing the Consumer to contact the Event Channel and “check for mail”. If there is a message in the Event Channel, that message will be returned as a CORBA::Any value, and the try_pull's CORBA::Boolean flag has_event will be set to true. The try_pull call is thus made from within the thread's “start” function in this way:
CORBA::Any* anyval;
CORBA::Boolean has_event = 0;
anyval = proxy_Supplier->try_pull(has_event);
If no event is waiting, the has_event flag is set to false and no value is returned; but the call does not block (as the pull function does), so it returns to the client immediately. This allows the client to continue doing other work while periodically checking to see if a new event message is waiting in the Event Channel's queue.

Once the has_event value is true and an Any value is retrieved, the Consumer must decide first what type it is, then extract that value from the Any wrapper in order to use it. The code to do that uses the Any's overloaded >>= operator. This strange-looking beast will attempt to extract the Any into the destination type. If the type contained in the Any is compatible with the destination type, the value is extracted from the Any; if not, null is returned. The usual way to check for the value is to do something like the following:

if( *anyval >>= shortval )
  {
    cerr << "Consumer: thread pulled short:
         " << shortval << endl;
  }
  else if( *anyval >>= doubleval )
  {
    cerr << setiosflags(ios::fixed);
    cerr << "Consumer:
         thread pulled double: " << doubleval << endl;
  }

In our case, when we extract the correct type from the Any, we print it out and immediately begin checking again for events through our try_pull call.

Our Supplier implementation is a bit simpler. After binding to the ORB, it creates an implementation of a class that implements the CORBA PushSupplier IDL:

class PushSupplierImpl :
virtual public CosEventComm::PushSupplier_skel
{
public:
PushSupplierImpl() { }
void disconnect_push_Supplier();
};
...
  PushSupplierImpl * Supplier =
  new PushSupplierImpl();

This class implements the IDL PushSupplier interface, which has only a single function to implement: disconnect_push_Supplier. The implementation object, PushSupplierImpl * Supplier, will be used later to connect to the Event Channel and register our interest in supplying events to the Channel.

Just as the Consumer started by finding the root Naming Context, our Supplier begins by calling resolve_initial_references. Using the IOR returned by resolve_initial_references, the Supplier can then narrow to the naming context object.

CORBA::Object_var nsobj =
orb->resolve_initial_references("NameService");
assert(! CORBA::is_nil(nsobj));
cerr << "Supplier: successful call to \
resolve_initial_references()" << endl;
CosNaming::NamingContext_var context =
CosNaming::NamingContext::_narrow(nsobj);
assert(! CORBA::is_nil(context));

Once the name is resolved and narrowed, the Supplier attempts to retrieve a SupplierAdmin object through a call to the event channel's for_suppliers function.

CosNaming::Name name;
name.length(1);
name[0].id = CORBA::string_dup("TestEventChannel");
name[0].kind = CORBA::string_dup("");
CORBA::Object_var obj;
...
obj = context->resolve(name);
...
CosEventChannelAdmin::EventChannel_var
event_channel;
CosEventChannelAdmin::SupplierAdmin_var
Supplier_admin;
...
event_channel =
CosEventChannelAdmin::EventChannel::_narrow(obj);
Supplier_admin = event_channel->for_suppliers();
Once the SupplierAdmin object is retrieved, its obtain_push_Consumer function is called in order for the Supplier to obtain a Proxy PushConsumer with which to communicate.
CosEventChannelAdmin::ProxyPushConsumer_var
proxy_Consumer;
...
proxy_Consumer =
Supplier_admin->obtain_push_Consumer();
Once a proxy is obtained, we then need to connect the Supplier to the proxy through this call:
proxy_Consumer->connect_push_Supplier(
CosEventComm::PushSupplier::_duplicate(Supplier));
This call registers our interest in providing the Event Channel with events. The IDL interface for the PushConsumer (the implementation of which ProxyPushConsumer inherits) is:
interface PushConsumer
{
void push(in any data) raises(Disconnected);
void disconnect_push_Consumer();
};
Once a proxy push Consumer has been obtained, calls may be made on its push function, passing in a CORBA::Any value. This is done quite simply:
CORBA::Any any;
any <<=(CORBA::ULong) 555555555;
proxy_Consumer->push(any);
At this point, the Any value is delivered to the Event Channel, which is responsible for making that event message available to the try_pull calls of the Consumer, described above. Thus, we have come full circle in our discussion of the Supplier/Consumer roles in interacting with the Event Service.

Our example was built using the egcs 1.1b C++ compiler and MICO 2.2.1. In order to build and run the example, once you have unpacked the tar file, you simply need to update the variable MICO_BASEDIR in the Makefile to point to your base Mico installation, then type make. This will build both the Supplier and Consumer. To run the application, we've provided a simple script that starts the rather lengthy MICO naming and event services for you automatically, then starts the Consumer (which creates the Event Channel), then the Supplier. To run the script, simply type runit. You will see the progress of the Supplier writing messages to the Event Channel, and the Consumer extracting them from the Event Channel; as it does so, it prints them out. Our Supplier will push, in succession, a long, a short, a double, a string, and finally another long (the number 13), which signals to the Consumer that it is finished. At that point, the Consumer thread exits and the applications are killed by the runit script.

Our next article will discuss an implementation of VisiBroker for Java that can be made available for development of clients and servers completely on Linux using Sun's JDK.

Resources

Home page for the Object Management Group: http://www.omg.org/

Introduction to CORBA: http://www.omg.org/news/begin.htm

The Free CORBA Page: http://adams.patriot.net/~tvalesky/freecorba.html

Java port for Linux: http://java.blackdown.org/

The CORBA FAQ: http://www.cerfnet.com/~mpcline/Corba-FAQ

Mark Shacklette is a principal with Pierce, Leverett & McIntyre in Chicago, specializing in distributed object technologies. He holds a degree from Harvard University and is currently finishing a Ph.D. in the Committee on Social Thought at the University of Chicago. He is an adjunct professor teaching UNIX at Oakton Community College. He lives in Des Plaines, Illinois with his wife, two sons and one cat. He can be reached at jmshackl@plm-inc.com.

Jeff Illian is a principal with Energy Mark, Inc. in Chicago, specializing in electric utility deregulation and distributed trading technologies. He holds a degree from Carnegie-Mellon University in Operations Research (Applied Mathematics). He lives in Cary, Illinois with his wife, son and daughter. He can be reached at jeff.illian@energymark.com.

______________________

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix