Implementing Pluggable Protocols for TAO

Fred Kuhns, Douglas C. Schmidt, Carlos O'Ryan, Ossama Othman, and Bruce Trask

Center for Distributed Object Computing
Washington University at St.Louis


Overview

To be an effective platform for performance-sensitive real-time and embedded applications, off-the-shelf CORBA middleware must preserve the communication-layer quality of service (QoS) properties of applications end-to-end. However, the standard CORBA GIOP/IIOP interoperability protocols are not well suited for applications that cannot tolerate the message footprint size, latency, and jitter associated with general-purpose messaging and transport protocols. Fortunately, the CORBA specification defines the notion of "Environmentally-Specific Inter-ORB Protocols" (ESIOPs) that can be used to integrate non-GIOP/IIOP protocols beneath an ORB.

To allow end-users and developers to take advantage of ESIOP capabilities, it is useful for an ORB to support a pluggable protocols framework that allows custom messaging and transport protocols to be configured flexibly and used transparently by applications. This document explains how to develop pluggable protocols using TAO's pluggable protocols framework. Here are some links that describe TAO's pluggable protocols framework, though not how to implement one:

../releasenotes/index.html#pp
http://www.cs.wustl.edu/~schmidt/PDF/PfHSN.pdf
http://www.cs.wustl.edu/~schmidt/PDF/pluggable_protocols.pdf


Table of Contents



Overview of Implementation of Pluggable Protocols for TAO

There are several basic implementation details that should be followed when implementing pluggable protocols for TAO. This section describes them.

The Hard Way

It is possible to implement a pluggable protocol for TAO without using any ACE components, or using existing pluggable protocols as a reference, but that is certainly more difficult than taking advantage of the code reuse offered by using existing ACE and TAO models and implementations.

The Path of Least Resistance

TAO takes advantage of the many useful encapsulations provided by ACE. These include ACE's IPC_SAP classes, Event Handlers and the operation dispatching features provided by the Reactor. However, in order to use these encapsulations some requirements must be satisfied.

Basic Requirements

To be able to successfully use the ACE components listed above, the underlying protocol used in the pluggable protocol for TAO should support the following features and characteristics:

Basics of Implementing a Pluggable Protocol for TAO

One of the easiest ways to implement a pluggable protocol for TAO is to do the following:

  1. Implement ACE IPC_SAP wrappers around the underlying protocol implementation. For example, ACE wraps the socket API to create an ACE_INET_Addr, ACE_SOCK_Acceptor, ACE_SOCK_Connector and ACE_SOCK_Stream. IPC_SAP wrappers for other implementations, such as OSI transport protocol layer 4, aka TP4, should be implemented similarly. A TP4 implementation could have an ACE_TP4_Addr, ACE_TP4_Acceptor, ACE_TP4_Connector and an ACE_TP4_Stream. Any new implementation should retain the interface provided by the base IPC_SAP classes in ACE.
  2. Once the above ACE IPC_SAP components have been implemented, creating the necessary TAO pluggable protocol components should be fairly easy. In fact, much of the code can be ``cut and pasted'' from existing TAO pluggable protocol implementations. For example, TAO's UIOP pluggable protocol was, for the most part, based entirely on the IIOP pluggable protocol code found in TAO's ``tao/IIOP_*'' source files. The only things that had to be changed for UIOP were the protocol prefix, address format and URL style IOR format. Each of these pluggable protocol characteristics is documented later in this documentation.

The next section goes into detail about TAO's pluggable protocol framework components and their public interfaces. It is our goal that no changes to ACE or TAO should be necessary to implement a pluggable protocol. Naturally, as with all frameworks, it's only possible to achieve this goal if (1) all possible use-cases are understood in advance or (2) the framework is generalized when confronted with new use-cases that weren't handled before. Therefore, we describe the conditions that must currently hold in order to develop and integrate a pluggable protocol for TAO.


Pluggable Protocol Framework Components

In order for a pluggable protocol to be usable by TAO without making any modifications to TAO itself, it must be implemented to provide the functionality dictated by TAO's pluggable protocols framework interface. This functionality is implemented within several components, namely the Acceptor, Connector, Connection Handler, Protocol Factory, Profile and Transport. This section describes each of them.

The Acceptor

Context

A server can accept connections at one or more endpoints, potentially using the same protocol for all endpoints. The set of protocols that an ORB uses to play the client role need not match the set of protocols used for the server role. Moreover, the ORB can even be a ``pure client,'' i.e., a client that only makes requests, in which case it can use several protocols to make requests, but receive no requests from other clients.

Problem

The server must generate an IOR that includes all possible inter-ORB and transport-protocol-specific profiles for which the object can be accessed. As with the client, it should be possible to add new protocols without changing the ORB.

Solution

We use the Acceptor pattern [1] to accept the connections. As with the Connector pattern, an Acceptor decouples the connection establishment from the processing performed on that connection. However, in the Acceptor pattern, the connection is accepted passively, rather than being initiated actively.

Applying the solution to TAO

Figure 1 illustrates how TAO's pluggable protocols framework leverages the design presented in Section [*]. The concrete ACE Service Handler created by the ACE Acceptor is responsible for implementing the External Polymorphism pattern [2] and encapsulating itself behind the Transport interface defined in our pluggable protocols framework.

Figure 1: Server Pluggable Protocol Class Diagram

Server

As discussed in Section [*], we use the Adapter pattern to leverage the ACE implementation of the Acceptors. This pattern also permits a seamless integration with the lower levels of the ORB. In the Acceptor pattern, the Acceptor object is a factory that creates Service Handlers. Service Handlers are responsible for performing I/O with their connected peers. In TAO's pluggable protocol framework, the Transport objects are Service Handlers implemented as abstract classes. This design shields the ORB from variations in the Acceptors, Connectors, and Service Handlers for each particular protocol.

When a connection is established, the concrete Acceptor creates the appropriate Connection Handler and IOP objects. The Connection Handler also creates a Transport object that functions as a bridge. As with the Connector, the Acceptor also acts as a bridge object, hiding the transport- and strategy-specific details of the acceptor.

Acceptor Implementation

TAO's Acceptor interface, shown below, is declared in the file <tao/Transport_Acceptor.h>. All Acceptor implementations must inherit from the TAO_Acceptor abstract base class.

class TAO_Export TAO_Acceptor
{
  // = TITLE
  //   Abstract Acceptor class used for pluggable protocols.
  //
  // = DESCRIPTION
  //   Base class for the Acceptor bridge calls.
public:
  TAO_Acceptor (CORBA::ULong tag);
 
  virtual ~TAO_Acceptor (void);
  // Destructor
 
  CORBA::ULong tag (void) const;
  // The tag, each concrete class will have a specific tag value.
 
  CORBA::Short priority (void) const;
  // The priority for this endpoint.
 
  virtual int open (TAO_ORB_Core *orb_core,
                    int version_major,
                    int version_minor,
                    const char *address,
                    const char *options = 0) = 0;
  // Method to initialize acceptor for address.
 
  virtual int open_default (TAO_ORB_Core *orb_core,
                            const char *options = 0) = 0;
  // Open an acceptor on the default endpoint for this protocol
 
  virtual int close (void) = 0;
  // Closes the acceptor
 
  virtual int create_mprofile (const TAO_ObjectKey &object_key,
                               TAO_MProfile &mprofile) = 0;
  // Create the corresponding profile for this endpoint.
 
  virtual int is_collocated (const TAO_Profile* profile) = 0;
  // Return 1 if the <profile> has the same endpoint as the acceptor.
 
  virtual CORBA::ULong endpoint_count (void) = 0;
  // Returns the number of endpoints this acceptor is listening on.  This
  // is used for determining how many profiles will be generated
  // for this acceptor.
 
protected:
  CORBA::Short priority_;
  // The priority for this endpoint
 
private:
  CORBA::ULong tag_;
  // IOP protocol tag.
 
};  
A description of each of the methods that must be implemented follows:

The Constructor.
Other than initializing members of a pluggable protocol Acceptor implementation, nothing else is really done in the constructor. For example, the TAO_IIOP_Acceptor constructor implementation is:

TAO_IIOP_Acceptor::TAO_IIOP_Acceptor (void)
  : TAO_Acceptor (TAO_TAG_IIOP_PROFILE),
    version_ (TAO_DEF_GIOP_MAJOR, TAO_DEF_GIOP_MINOR),
    orb_core_ (0),
    base_acceptor_ (),
    creation_strategy_ (0),
    concurrency_strategy_ (0),
    accept_strategy_ (0)
{
}

Note that initializing the TAO_Acceptor base class with a tag value is required. Additional information about tags is available in the tags section of this document. In this case, TAO_TAG_IIOP_PROFILE is defined to be the OMG assigned tag for the CORBA IIOP protocol. Until a tag that uniquely identifies the protocol is assigned, a tag value that isn't used by any other protocol can be used in the meantime.

open.
The open method initializes the acceptor, i.e. sets the protocol version to use (if supported), parses any protocol-specific options (if any) and creates the endpoint passed to it.

The address specified by using the -ORBEndpoint ORB option is passed directly to this method. If more than one address is specified within a given -ORBEndpoint option then each address is passed one by one to this method by the pluggable protocols framework. For example, the following -ORBEndpoint command line option:

-ORBEndpoint iiop://[email protected],[email protected]

will cause the following open method invocations to occur:

open (orb_core, 1, 1, "foo.xyz.com", 0)
open (orb_core, 1, 0, "bar.abc.com", 0)

Extracting individual addresses from an -ORBEndpoint option is handled by TAO's pluggable protocols framework. It is up to the pluggable protocol to handle the address passed to this method.

open_default.
Each pluggable protocol should have the ability to open a default endpoint. For example, it should be possible to make the pluggable protocol open an endpoint without specifying an address, in which case an address is chosen by the pluggable protocol. This method is invoked when -ORBEndpoint options such as the following are used:

-ORBEndpoint iiop://

In this case, an IIOP endpoint will be created on the local host. The port will be chosen by the IIOP pluggable protocol. open_default will invoked in the following manner:

open_default (orbcore, 0)

close.
The close method is self-explanatory. It simply shuts down any open endpoints, and recovers resources as necessary. This method is automatically invoked by the pluggable protocols framework when the ORB associated with that endpoint is shut down.

create_mprofile.
The create_mprofile method creates a protocol-specific Profile object and gives ownership of that profile to the TAO_MProfile object, basically a container that holds multiple profiles, passed to it. The code for this method is typically ``boilerplate,'' i.e. it can be based almost entirely on TAO's existing pluggable protocol implementations of create_profile. Here is how it is implemented in TAO's IIOP acceptor:

int
TAO_IIOP_Acceptor::create_mprofile (const TAO_ObjectKey &object_key, 
TAO_MProfile &mprofile) 

  // @@ we only make one for now 
  int count = mprofile.profile_count (); 
  if ((mprofile.size () - count) < 1 
      && mprofile.grow (count + 1) == -1) 
    return -1; 
 
  TAO_IIOP_Profile *pfile = 0; 
  ACE_NEW_RETURN (pfile, 
                  TAO_IIOP_Profile (this->host_.c_str (), 
                                    this->address_.get_port_number (), 
                                    object_key, 
                                    this->address_, 
                                    this->version_, 
                                    this->orb_core_), 
                  -1); 
 
  if (mprofile.give_profile (pfile) == -1) 
    { 
      pfile->_decr_refcnt (); 
      pfile = 0; 
      return -1; 
    } 
 
  if (this->orb_core_->orb_params ()->std_profile_components () == 0) 
    return 0; 
 
  pfile->tagged_components ().set_orb_type (TAO_ORB_TYPE); 
 
  CONV_FRAME::CodeSetComponentInfo code_set_info; 
  code_set_info.ForCharData.native_code_set  = 
    TAO_DEFAULT_CHAR_CODESET_ID; 
  code_set_info.ForWcharData.native_code_set = 
    TAO_DEFAULT_WCHAR_CODESET_ID; 
  pfile->tagged_components ().set_code_sets (code_set_info); 
 
  pfile->tagged_components ().set_tao_priority (this->priority ()); 
 
  return 0;

}

Most of the code that is common to all pluggable protocols will be factored out of this method in the near future.

is_collocated.
The is_collocated method checks if the Profile has the same endpoint as the Acceptor. Assuming ACE is used as the underlying layer between the operating system calls and a pluggable protocol, this code is also ``boilerplate.'' TAO's IIOP implementation does the following:

int 
TAO_IIOP_Acceptor::is_collocated (const TAO_Profile *pfile) 

  const TAO_IIOP_Profile *profile = 
    ACE_dynamic_cast(const TAO_IIOP_Profile *, 
                     pfile); 
 
  // compare the port and sin_addr (numeric host address) 
  return profile->object_addr () == this->address_; 
}

endpoint_count.
endpoint_count returns the number of endpoints the Acceptor is listening on. This is used for determining how many Profiles will be generated for this Acceptor. Currently, all of TAO's pluggable protocols simply return 1.

The Connector

Context

When a client references an object, the ORB must obtain the corresponding profile list, which is derived from the IOR and a profile ordering policy, and transparently establish a connection to the server.

Problem

There can be one or more combinations of inter-ORB and transport protocols available in an ORB. For a given profile, the ORB must verify the presence of the associated IOP and transport protocol, if available. It must then locate the applicable Connector and delegate it to establish the connection.

Solution

We use the Connector pattern [1] to actively establish a connection to a remote object. This pattern decouples the connection establishment from the processing performed after the connection is successful. As before, the Connector Registry shown in Figure 2 is used

Figure 2: Connection Establishment Using Multiple Pluggable Protocols

pp_e2e

to locate the right Connector for the current profile. The actual profile selected for use will depend on the set of Policies active at the time of connection establishment. However, once a profile is selected, the connector registry matches the profile type, represented by a well known tag, with an instance of a concrete Connector.

Applying the solution in TAO

As described in Section [*], Connectors are adapters for the ACE implementation of the Connector pattern. Thus, they are typically lightweight objects that simply delegate to a corresponding ACE component.

Figure 3 shows the base classes and their relations for IIOP.

Figure 3: Client Pluggable Protocol Class Diagram

Client

This figure shows an explicit co-variance between the Profile and the Connectors for each protocol. In general, a Connector must downcast the Profile to its specific type. This downcast is safe because profile creation is limited to the Connector and Acceptor registries. In both cases, the profile is created with a matching tag. The tag is used by the Connector Registry to choose the Connector that can handle each profile.

As shown in the same figure, the Connector Registry manipulates only the base classes. Therefore, new protocols can be added without requiring any modification to the existing pluggable protocols framework. When a connection is successfully established, the Profile is passed a pointer to the particular IOP object and to the Transport objects that were created.

Connector Implementation

TAO's Connector interface, shown below, is declared in the file < tao/Transport_Connector.h>. All Connector implementations must inherit from the TAO_Connector abstract base class.

class TAO_Export TAO_Connector 

  // = TITLE 
  //   Generic Connector interface definitions. 
  // 
  // = DESCRIPTION 
  //   Base class for connector bridge object. 
public: 
 
  TAO_Connector (CORBA::ULong tag); 
  // default constructor. 
 
  virtual ~TAO_Connector (void); 
  // the destructor. 
 
  CORBA::ULong tag (void) const; 
  // The tag identifying the specific ORB transport layer protocol. 
  // For example TAO_TAG_IIOP_PROFILE = 0.  The tag is used in the 
  // IOR to identify the type of profile included. IOR -> {{tag0, 
  // profile0} {tag1, profole1} ...}  GIOP.h defines typedef 
  // CORBA::ULong TAO_IOP_Profile_ID; 
 
  int make_mprofile (const char *ior, 
                     TAO_MProfile &mprofile, 
                     CORBA::Environment &ACE_TRY_ENV); 
  // Parse a string containing a URL style IOR and return an 
  // MProfile. 
 
  virtual int open (TAO_ORB_Core *orb_core) = 0; 
  //  Initialize object and register with reactor. 
 
  virtual int close (void) = 0; 
  // Shutdown Connector bridge and concreate Connector. 
 
  virtual int connect (TAO_Profile *profile, 
                       TAO_Transport *&, 
                       ACE_Time_Value *max_wait_time) = 0; 
  // To support pluggable we need to abstract away the connect() 
  // method so it can be called from the GIOP code independant of the 
  // actual transport protocol in use. 
 
  virtual int preconnect (const char *preconnections) = 0; 
  // Initial set of connections to be established. 
 
  virtual TAO_Profile *create_profile (TAO_InputCDR& cdr) = 0; 
  // Create a profile for this protocol and initialize it based on the 
  // encapsulation in <cdr> 
 
  virtual int check_prefix (const char *endpoint) = 0; 
  // Check that the prefix of the provided endpoint is valid for use 
  // with a given pluggable protocol. 
 
  virtual char object_key_delimiter (void) const = 0; 
  // Return the object key delimiter to use or expect. 
 
#if defined (TAO_USES_ROBUST_CONNECTION_MGMT) 
  virtual int purge_connections (void) = 0; 
  // Purge "old" connections. 
#endif /* TAO_USES_ROBUST_CONNECTION_MGMT */ 
 
protected: 
  virtual void make_profile (const char *endpoint, 
                             TAO_Profile *&, 
                             CORBA::Environment &ACE_TRY_ENV) = 0; 
  // Create a profile with a given endpoint. 
 
private: 
  CORBA::ULong tag_; 
  // IOP protocol tag. 
};
A description of each of the methods that must be implemented follows:

The Constructor.
As with the Acceptor constructor, the TAO_Connector base class should be initialized with the tag associated with the pluggable protocol in question. Here is TAO's IIOP pluggable protocol Connector constructor:

TAO_IIOP_Connector::TAO_IIOP_Connector (void) 
  : TAO_Connector (TAO_TAG_IIOP_PROFILE), 
    orb_core_ (0), 
    base_connector_ () 

}

open.
The open method simply opens the underlying connector. This is typically an ACE_Strategy_Connector template instance. For example, TAO's IIOP pluggable protocol uses an

ACE_Strategy_Connector<TAO_IIOP_Client_Connection_Handler, 
                       ACE_SOCK_CONNECTOR>
as its underlying connection strategy. No connection establishment occurs here. Note that, if ACE is used to implement a Connector, this method should also register the Connection Handler with an ACE_Reactor.

close.
close simply closes the underlying Connector bridge and the concrete Connector.

connect.
The connect method actively establishes a connection to the endpoint encoded within the IOR in use. It should first verify that the tag contained within Profile passed to it matches the tag associated with the pluggable protocol. If the tags do not match then an attempt use a Profile for another pluggable protocol was made.

The Transport object must also be set in this method. This is generally done by using the transport method found within the Connection Handler being used.

preconnect.
The preconnect method is invoked when the -ORBPreconnect ORB option is used. It causes a blocking connection to be made to the specified address. Multiple connections can be made to the same endpoint. For example, the following -ORBPreconnect option will cause two IIOP blocking connections to be made to the specified host and port:

-ORBPreconnect iiop://foo.bar.com:1234,foo.bar.com:1234

Much of the preconnect parsing code in TAO's current preconnect implementations will be factored out into a common method. Pluggable protocols will still be responsible for parsing addresses, just like the open method in Acceptors (not Connectors).

check_prefix.
check_prefix checks that the protocol prefix in a URL style IOR or preconnect endpoint is valid for use with a given pluggable protocol. For example, the check_prefix implementation in TAO's IIOP pluggable protocol looks like the following:

int 
TAO_IIOP_Connector::check_prefix (const char *endpoint) 

  // Check for a valid string 
  if (!endpoint || !*endpoint) 
    return -1;  // Failure 
 
  const char *protocol[] = { "iiop", "iioploc" }; 
 
  size_t slot = ACE_OS::strchr (endpoint, ':') - endpoint; 
 
  size_t len0 = ACE_OS::strlen (protocol[0]); 
  size_t len1 = ACE_OS::strlen (protocol[1]); 
 
  // Check for the proper prefix in the IOR.  If the proper prefix 
  // isn't in the IOR then it is not an IOR we can use. 
  if (slot == len0 
      && ACE_OS::strncasecmp (endpoint, protocol[0], len0) == 0) 
    return 0; 
  else if (slot == len1 
           && ACE_OS::strncasecmp (endpoint, protocol[1], len1) == 0) 
    return 0; 
 
  return -1; 
  // Failure: not an IIOP IOR 
  // DO NOT throw an exception here. 
}

It checks that the protocol prefix in a URL style IOR (e.g. corbaloc:iiop:foo.bar.com:1234/...) or preconnect endpoint matches the one(s) supported by the IIOP pluggable protocol, in this case ``iiop'' and ``iioploc.'' If no match occurs then return an error condition (-1). Note that the protocol prefix ``iiop'' is only there for backward compatibility. It may be removed in future TAO releases.

This method is important for TAO's implementation of the CORBA Interoperable Naming Service.

object_key_delimiter.
The object key delimiter within a URL style IOR is the character that separates the address from the object key. For example, in the following IIOP URL style IOR:

corbaloc:iiop:[email protected]:1234/some_object_key

the object key delimiter is `/.' However, this character is not suitable for all pluggable protocols, such as TAO's UIOP pluggable protocol, because addresses within a URL style IOR may contain that very same character. A typical TAO UIOP URL style IOR may look something like:

uioploc:///tmp/foobar|some_other_object_key

In this case, the object key delimiter is a vertical bar `|' because using the same object key delimiter that IIOP uses `/' would cause the point where the UIOP rendezvous point ``/tmp/foobar'' ends and where the object key ``some_other_object_key'' begins to be ambiguous. For instance, if an IIOP object key delimiter was used in a UIOP URL style IOR as follows:

uioploc:///tmp/foobar/some_other_object_key

it then becomes impossible to tell if the rendezous point is ``/tmp'' or ``/tmp/foobar,'' and similarly for the object key, hence the need for an object key delimiter other than `/.'

In general, this method simply returns a static variable in the associated Profile that contains the object key delimiter appropriate for the given pluggable protocol.

create_profile.
This method creates and initializes a profile using the provided CDR stream. Most of this code is also ``boilerblate.'' As such, it may be factored out in future TAO releases.

make_profile.
make_profile is another method that can essentially be ``cut and pasted'' from existing TAO pluggable protocols. It is simply a Profile factory. The Profile it creates is initialized with an object reference of the form:

N.n@address/object_key

or

address/object_key

where ``N.n'' are the major and minor protocol versions, respectively, and the `/' is the protocol-specific object key delimiter.

The Profile Object

TAO Profile objects encapsulate all of the methods and members necessary to create and parse a protocol-specific IOR, in addition to representing object address and location information. TAO Profiles are based on CORBA IOR definitions.

All protocol-specific Profile implementations should inherit from the TAO_Profile abstract base class. The TAO_Profile interface is declared in the file <tao/Profile.h>. Its interface follows. Only the methods that must be implemented are shown:

class TAO_Export TAO_Profile 

  // = TITLE 
  //   Defines the Profile interface 
  // 
  // = DESCRIPTION 
  //   An abstract base class for representing object address or location 
  //   information.  This is based on the CORBA IOR definitions. 
  // 
public: 
 
  virtual int parse_string (const char *string, 
                            CORBA::Environment &ACE_TRY_ENV) = 0; 
  // Initialize this object using the given input string. 
  // Supports URL style of object references 
 
  virtual char* to_string (CORBA::Environment &ACE_TRY_ENV) = 0; 
  // Return a string representation for this profile.  client must 
  // deallocate memory. 
 
  virtual int decode (TAO_InputCDR& cdr) = 0; 
  // Initialize this object using the given CDR octet string. 
 
  virtual int encode (TAO_OutputCDR &stream) const = 0; 
  // Encode this profile in a stream, i.e. marshal it. 
 
  virtual TAO_ObjectKey *_key (void) const = 0; 
  // Obtain the object key, return 0 if the profile cannot be parsed. 
  // The memory is owned by the caller! 
 
  virtual CORBA::Boolean is_equivalent (const TAO_Profile* other_profile) = 0; 
  // Return true if this profile is equivalent to other_profile.  Two 
  // profiles are equivalent iff their key, port, host, object_key and 
  // version are the same. 
 
  virtual CORBA::ULong hash (CORBA::ULong max, 
                             CORBA::Environment &ACE_TRY_ENV) = 0; 
  // Return a hash value for this object. 
 
  virtual int addr_to_string (char *buffer, size_t length) = 0; 
  // Return a string representation for the address.  Returns 
  // -1 if buffer is too small.  The purpose of this method is to 
  // provide a general interface to the underlying address object's 
  // addr_to_string method.  This allowsthe protocol implementor to 
  // select the appropriate string format. 
 
  virtual void reset_hint (void) = 0; 
  // This method is used with a connection has been reset requiring 
  // the hint to be cleaned up and reset to NULL. 
};
TAO's existing pluggable protocols have a static member containing the object key delimiter specific to the given pluggable protocol, in addition to a static protocol prefix accessor method that simply returns a pointer to a static string containing the protocol prefix specific to the pluggable protocol. Note that both of these members will always remain constant so there is no problem in making them static.

Theses static member are:

object_key_delimiter.
This variable contains the object key delimiter specific to the given pluggable protocol. A typical definition looks like:

const char TAO_IIOP_Profile::object_key_delimiter = '/';
prefix.
This method simply returns a pointer to a static string that contains the protocol prefix specific to the pluggable protocol in use. The static string for the IIOP pluggable protocol is:

static const char prefix_[] = "iiop";

The IIOP prefix method is:

const char * 
TAO_IIOP_Profile::prefix (void) 

  return ::prefix_; 
}
Note that it not strictly necessary to implement equivalent versions of these static members. TAO's implementation just happens to use them. However, pluggable protocol implementations that are based on TAO's pluggable protocols may need them.

Common to all concrete Profile implementations are a set of methods that must be implemented. A description of each of these methods:

The Constructors.
TAO's existing pluggable protocols each implement several constructors in their concrete Profile classes. While pluggable protocols are not strictly required to implement analogs to all of the constructors in existing TAO pluggable protocols, it is generally a good idea to do so. All of the constructors should initialize the TAO_Profile abstract base class with the tag associated with the pluggable protocol. The object key should also be set during Profile construction.

parse_string.
parse_string initialize a Profile object using the provided string. The string passed to this method has the format:

N.n@address/object_key

or

address/object_key

where the address and the object key delimiter `/' are pluggable protocol specific. The parse_string method must be able to extract the protocol version (even if it isn't used), the address and the object key from the provided string. It is generally a good idea to use the parse_string methods in TAO's existing pluggable protocols as a reference when implementing this method.

to_string.
This method returns the URL style representation of the object reference encapsulated by the Profile object. Most of this code is also ``boilerplate.'' However, the address part of the URL style IOR, returned by this method should be specific to the pluggable protocol. For example, the address in the following IIOP URL style IOR:

corbaloc:iiop:[email protected]:1234/yet_another_object_key

is ``foo.bar.com:1234.'' Much of the in TAO's existing pluggable protocols may also be factored out because that code is common to all pluggable protocols. The URL style IOR created by this method should be usable by TAO's Interoperable Naming Service via the -ORBInitRef ORB option.

decode.
The input CDR stream reference passed to this method is used to initialize the Profile object, by extracting (decoding all object reference information found within the CDR stream. Care must be taken to extract the information in the correct order. Use existing TAO pluggable protocol decode implementations as a reference.

The decode method performs the inverse operation of the encode method.

encode.
This method inserts (encodes all of the information encapsulated by the Profile object in to the provided output CDR stream. Care must be taken to insert the information in the correct order. Use existing TAO pluggable protocol encode implementations as a reference.

The encode method performs the inverse operation of the decode method.

_key.
The _key method constructs a TAO_ObjectKey object that is a copy of the object key found within the Profile object, and returns a pointer to the newly create TAO_ObjectKey object. Note that the caller owns the memory allocated by this method. TAO's IIOP _key implementation is:

ACE_INLINE TAO_ObjectKey * 
TAO_IIOP_Profile::_key (void) const 

  TAO_ObjectKey *key = 0; 
 
  ACE_NEW_RETURN (key, 
                  TAO_ObjectKey (this->object_key_), 
                  0); 
 
  return key; 
}

is_equivalent.
The is_equivalent method implements the CORBA specified method of the same name. It should return true if this profile is equivalent to the profile to which it is being compared. Two profiles are equivalent if and only if their tag, version, address and object key are the same.

hash.
The hash method implements the CORBA specified method of the same name. It is expected to return 32 bit unsigned integer (CORBA::ULong) that uniquely identifies the CORBA object referenced by the Profile object. This method accepts an argument that specifies the largest value the hash can be.

Any algorithm deemed suitable to provide the required functionality may be used. The ACE class in the ACE library provides implementations of several hash algorithms.

addr_to_string.
addr_to_string returns a string representation of the address. It should return -1 if the supplied buffer is too small. The stringified address should have the same form that the address in the URL style IOR has. This method should be reentrant.

reset_hint.
The reset_hint method resets the pointer to a successfully used Connection Handler. If no pointer to such a Connection Handler, i.e. a hint is provided by the pluggable protocol then this method may be implemented as a ``no-op.'' A pluggable protcol that does not provide a cached Connection Handler will not be able to take advantage of improved Connector table look up times.

The Protocol_Factory Object

TAO's uses the ACE's Service Configurator implementation [3] to dynamically load pluggable protocol factories. A Protocol_Factory is responsible for creating the Acceptor and Connector for the given pluggable protocol. TAO iterates through the list of loaded Protocol Factories and invokes a factory operation that creates the desired object: an Acceptor on the server-side, and a Connector on the client-side.

All Protocol_Factory implementations should be derived from the TAO_Protocol_Factory abstract base class defined in <tao/Protocol_Factory.h>. The TAO_Protocol_Factory interface is shown below:

class TAO_Export TAO_Protocol_Factory : public ACE_Service_Object 

public: 
  TAO_Protocol_Factory (void); 
  virtual ~TAO_Protocol_Factory (void); 
 
  virtual int init (int argc, char *argv[]); 
  // Initialization hook. 
 
  virtual int match_prefix (const ACE_CString &prefix); 
  // Verify prefix is a match 
 
  virtual const char *prefix (void) const; 
  // Returns the prefix used by the protocol. 
 
  virtual char options_delimiter (void) const; 
  // Return the character used to mark where an endpoint ends and 
  // where its options begin. 
 
  // Factory methods 
  virtual TAO_Acceptor  *make_acceptor (void); 
  // Create an acceptor 
 
  virtual TAO_Connector *make_connector  (void); 
  // Create a connector 
 
  virtual int requires_explicit_endpoint (void) const = 0; 
  // Some protocols should not create a default endpoint unless the 
  // user specifies a -ORBEndpoint option. For example, local IPC 
  // (aka UNIX domain sockets) is unable to remove the rendesvouz 
  // point if the server crashes.  For those protocols is better to 
  // create the endpoint only if the user requests one. 
};
Each of the important methods to be implemented are described below:

init.
The init method is invoked immediately after the pluggable protocol factory is loaded. The Service Configurator passes Protocol Factory options specified in a Service Configurator configuration file (e.g. svc.conf) to this method. The passing convention is essentially the same as command line argc/argv argument passing convention. The only difference lies in the fact that the option parsing should begin at argv[0] instead of argv[1]. This differs from the standard command line passing convention where argv[0] is the name of the program currently being run. In any case, the init method allows protocol-specific options to be implemented. Once passed to this method, the pluggable protocol can use them to, for example, enable or disable certain features or flags within the Protocol Factory. Other pluggable protocol components can then use these flags or features as necessary.

A typical Service Configurator file line that loads a pluggable protocol dynamically, and passes arguments to the init method would look like:

dynamic IIOP_Factory Service_Object * TAO:_make_TAO_IIOP_Protocol_Factory() "-Foo Bar"

In the above example, the arguments ``-Foo'' and ``Bar'' would be passed as argv[0] and argv[1], respectively, to the Protocol_Factory init method.

match_prefix.
This method verifies that protocol prefix contained in the string passed to matches the one used by the pluggable protocol. A typical implementation, such as the one used by the IIOP pluggable protocol, looks like:

static const char prefix_[] = "iiop"; 
 
int 
TAO_IIOP_Protocol_Factory::match_prefix (const ACE_CString &prefix) 

  // Check for the proper prefix for this protocol. 
  return (ACE_OS::strcasecmp (prefix.c_str (), ::prefix_) == 0); 
}

prefix.
The prefix method simply returns a pointer to the static string containing the protocol prefix used by the pluggable protocol.

options_delimiter.
The options_delimiter method is similar to the object_key_delimiter method found in the Connector. However, it used to delimit where an endpoint ends and where its options begin. For example, the following -ORBEndpoint option specifies a endpoint-specific priority:

-ORBEndpoint iiop://[email protected]/priority=25

To get around ambiguities in endpoints that can have a `/' character in them, the options_delimiter method can be used to determine what character to be used. For example, TAO's UIOP pluggable protocol implementation defines the following:

char 
TAO_UIOP_Protocol_Factory::options_delimiter (void) const 

  return '|'; 
}

An endpoint option for the UIOP pluggable protocol can look like the following:

-ORBEndpoint uiop://1.1@/tmp/foo|priority=25

Notice that the `|' character is used to mark where the rendezvous point ends and where the endpoint-specific options begin.

options_delimiter is a server-side related method. It is of no use to the client-side.

make_acceptor.
The make_acceptor method is a factory method that returns a pointer to a dynamically allocated Acceptor specific to the pluggable protocol to which the Protocol_Factory object belongs. TAO's UIOP pluggable protocol implementation defines this method as follows:

TAO_Acceptor * 
TAO_UIOP_Protocol_Factory::make_acceptor (void) 

  TAO_Acceptor *acceptor = 0; 
 
  ACE_NEW_RETURN (acceptor, 
                  TAO_UIOP_Acceptor, 
                  0); 
 
  return acceptor; 
}

make_connector.
The make_connector method is a factory method that returns a pointer to a dynamically allocated Connector specific to the pluggable protocol to which the Protocol_Factory object belongs. TAO's UIOP pluggable protocol implementation defines this method as follows:

TAO_Connector * 
TAO_UIOP_Protocol_Factory::make_connector (void) 

  TAO_Connector *connector = 0; 
 
  ACE_NEW_RETURN (connector, 
                  TAO_UIOP_Connector, 
                  0); 
 
  return connector; 
}

requires_explicit_endpoint.
Some protocols should not create a default endpoint unless the user specifies a -ORBEndpoint option. For example, local IPC (aka UNIX domain sockets) is unable to remove the rendesvouz point if the server crashes. For those protocols, it is better to create the endpoint only if the user requests one. This method is queried by TAO before creating a default acceptor during ORB initialization. If it returns 1 then no default endpoint should be created.
Service Objects, such as the Protocol_Factory, must be declared in a certain way. ACE's Service Configurator implementation provides two macros to ensure that the required additional declarations are made to make an object have to the correct interface. The two macros are ACE_STATIC_SVC_DECLARE and ACE_FACTORY_DECLARE. Typical usage of these declaration macros is demonstrated by TAO's UIOP pluggable protocol implementation:

ACE_STATIC_SVC_DECLARE (TAO_UIOP_Protocol_Factory) 
ACE_FACTORY_DECLARE (TAO, TAO_UIOP_Protocol_Factory)
also required. These are provided by ``DEFINE'' counterparts of the above two declaration macros. An example of how to use them follows:

ACE_STATIC_SVC_DEFINE (TAO_UIOP_Protocol_Factory, 
                       ASYS_TEXT ("UIOP_Factory"), 
                       ACE_SVC_OBJ_T, 
                       &ACE_SVC_NAME (TAO_UIOP_Protocol_Factory), 
                       ACE_Service_Type::DELETE_THIS | 
                          ACE_Service_Type::DELETE_OBJ, 
                       0) 
 
ACE_FACTORY_DEFINE (TAO, TAO_UIOP_Protocol_Factory)
Notice that the macro arguments above have corresponding Service Configurator configuration file entries:

dynamic UIOP_Factory Service_Object * TAO:_make_TAO_UIOP_Protocol_Factory() "" 
static Resource_Factory "-ORBProtocolFactory UIOP_Factory"

The Transport Object

Context

It is desirable to provide for alternative mappings between different ORB messaging protocols and ORB transport adaptors. For example, a single ORB messaging protocol such as GIOP can be mapped to any reliable, connection-oriented transport protocol, such as TCP or TP4. Alternatively, a single transport protocol can be the basis for alternative instantiations of ORB messaging protocols, e.g., different versions of GIOP differing in the number and types of messages, as well as in the format of those messages.

An ORB messaging protocol imposes requirements on any underlying network transport protocols. For instance, the transport requirements assumed by GIOP, see Section [*], require the underlying network transport protocol to support a reliable, connection-oriented byte-stream. These requirements are fulfilled by TCP thus leading to the direct mapping of GIOP onto this transport protocol. However, alternative network transport protocols such as ATM with AAL5 encapsulation may be more appropriate in some environments. In this case, the messaging implementation will have to provide the missing semantics, such as reliability, in order to use GIOP.

Problem

The ORB Messaging protocol implementations must be independent of the adaptation layer needed for transports that do not satisfy all their requirements. Otherwise, the same messaging protocol may be re-implemented needlessly for each transport, which is time-consuming, error-prone, and time/space inefficient. Likewise, for those transports that can support multiple ORB Messaging protocols, it must be possible to isolate them from the details of the ORB messaging implementation. Care must be taken, however, because not all ORB Messaging protocols can be used with all transport protocols, i.e., some mechanism is needed to ensure only semantically compatible protocols are configured [4].

Figure 4: Client Inter-ORB and Transport Class Diagram

pp_iopc

Solution

Use the Layers architecture pattern [5], which decomposes the system into groups of components, each one at a different level of abstraction.1 For the client, the ORB uses a particular ORB messaging protocol to send a request. This ORB messaging protocol delegates part of the work to the transport adapter component that completes the message and sends it to the server. If the low-level transport in use, e.g., ATM, UDP, TCP/IP, etc., does not satisfy the requirements of the ORB messaging protocol, the ORB transport adapter component can implement them.

In the server, the transport adapter component receives data from the underlying communication infrastructure, such as sockets or shared memory, and it passes the message up to the ORB messaging layer. As with the client, this layer can be very lightweight if the requirements imposed by the ORB messaging layer are satisfied by the underlying network transport protocol. Otherwise, it must implement those missing requirements by building them into the concrete transport adapter component.

Figure 5: Server Inter-ORB and Transport Class Diagram

pp_iops

Applying the solution in TAO

As shown in Figure 4, TAO implements the messaging protocol and the transport protocol in separate components. The client ORB uses the current profile to find the right transport and ORB messaging implementations. The creation and initialization of these classes is controlled by the Connector, with each Connector instance handling a particular ORB messaging/transport tuple.

Figure 5 illustrates how the server's implementation uses the same transport classes, but with a different relationship. In particular, the transport class calls back the messaging class when data is received from the IPC mechanism. As with the client, a factory, in this case the Acceptor, creates and initializes these objects.

Transport Implementation

A Transport implements external polymorphism [2] over the Client_Connection_Handler and the Service_Connection_Handler objects described in the Connection_Handler section of this document. A Connection_Handler is simply a template instantiation of ACE_Svc_Handler<>, but they don't do much work. They just delegate on the Transport classes. This somewhat strange design is easy to understand once it is realized that not all ACE_Svc_Handler<> classes are type compatible (except in their most basic ACE_Event_Handler form) so they must be wrapped using the TAO_Transport class.

All TAO pluggable protocol Transport classes must inherit from the TAO_Transport abstract base class defined in <tao/Transport.h>. The TAO_Transport interface is shown below. Again, only the methods that should be implemented for a given pluggable protocol are shown:

class TAO_Export TAO_Transport 

  // = TITLE 
  //   Generic definitions for the Transport class. 
  // 
  // = DESCRIPTION 
  //   The transport object is created in the Service handler 
  //   constructor and deleted in the service handler's destructor!! 
 
public: 
  TAO_Transport (CORBA::ULong tag, 
                 TAO_ORB_Core *orb_core); 
  // default creator, requres the tag value be supplied. 
 
  virtual ~TAO_Transport (void); 
  // destructor 
 
  virtual void close_connection (void) = 0; 
  // Call the corresponding connection handler's <close> 
  // method. 
 
  virtual int idle (void) = 0; 
  // Idles the corresponding connection handler. 
 
  virtual ACE_HANDLE handle (void) = 0; 
  // This method provides a way to gain access to the underlying file 
  // handle used by the reactor. 
 
  virtual ACE_Event_Handler *event_handler (void) = 0; 
  // This method provides a way to gain access to the underlying event 
  // handler used by the reactor. 
 
  virtual ssize_t send (TAO_Stub *stub, 
                        const ACE_Message_Block *mblk, 
                        const ACE_Time_Value *s = 0) = 0; 
  virtual ssize_t send (const ACE_Message_Block *mblk, 
                        const ACE_Time_Value *s = 0) = 0; 
  // Write the complete Message_Block chain to the connection. 
  // @@ The ACE_Time_Value *s is just a place holder for now.  It is 
  // not clear this this is the best place to specify this.  The actual 
  // timeout values will be kept in the Policies. 
 
  virtual ssize_t send (const u_char *buf, 
                        size_t len, 
                        const ACE_Time_Value *s = 0) = 0; 
  // Write the contents of the buffer of length len to the connection. 
 
  virtual ssize_t recv (char *buf, 
                        size_t len, 
                        const ACE_Time_Value *s = 0) = 0; 
  // Read len bytes from into buf. 
  // @@ The ACE_Time_Value *s is just a place holder for now.  It is 
  // not clear this this is the best place to specify this.  The actual 
  // timeout values will be kept in the Policies. 
 
  virtual void start_request (TAO_ORB_Core *orb_core, 
                              const TAO_Profile *profile, 
                              TAO_OutputCDR &output, 
                              CORBA::Environment &ACE_TRY_ENV = 
                                  TAO_default_environment ()) 
    ACE_THROW_SPEC ((CORBA::SystemException)); 
  // Fill into <output> the right headers to make a request. 
 
  virtual void start_locate (TAO_ORB_Core *orb_core, 
                             const TAO_Profile *profile, 
                             CORBA::ULong request_id, 
                             TAO_OutputCDR &output, 
                             CORBA::Environment &ACE_TRY_ENV = 
                                 TAO_default_environment ()) 
    ACE_THROW_SPEC ((CORBA::SystemException)); 
  // Fill into <output> the right headers to make a locate request. 
 
  virtual int send_request (TAO_Stub *stub, 
                            TAO_ORB_Core *orb_core, 
                            TAO_OutputCDR &stream, 
                            int twoway, 
                            ACE_Time_Value *max_time_wait) = 0; 
  // Depending on the concurrency strategy used by the transport it 
  // may be required to setup state to receive a reply before the 
  // request is sent. 
  // Using this method, instead of send(), allows the transport (and 
  // wait strategy) to take appropiate action. 
 
  virtual int handle_client_input (int block = 0, 
                                   ACE_Time_Value *max_wait_time = 0); 
  // Read and handle the reply. Returns 0 when there is Short Read on 
  // the connection. Returns 1 when the full reply is read and 
  // handled. Returns -1 on errors. 
  // If <block> is 1, then reply is read in a blocking manner. 
 
  virtual int register_handler (void); 
  // Register the handler with the reactor. Will be called by the Wait 
  // Strategy if Reactor is used  for that strategy. Default 
  // implementation out here returns -1 setting <errno> to ENOTSUP. 
 
};
Each method is described below:

The Constructor.
As with all of the TAO pluggable protocol framework components described so far, the constructor for the concrete Transport object should also initialize the TAO_Transport base class with the tag corresponding to the pluggable protocol.

close_connection.
The underlying Connection Handler's close method is invoked my this method.

idle.
This method idles the underlying Connection Handler.

handle.
This method returns the underlying file handle used by the Reactor.

event_handler.
This method returns the underlying Event Handler used by the Reactor.

send.
The send writes data to the connection established in the underlying transport, such as a TCP connection in the IIOP pluggable protocol. Three versions of this method must be implemented. Two of them write data contained within an ACE_Message_Block and the remaining simply sends the data within a buffer of a given length.

When implementing this method, it is important to be aware of the correct underlying send or write operation to use. Some of TAO's existing pluggable protocols use the send calls provided by the operating system because no further processing of the data being sent is necessary. However, other protocols may process perform additional operations on the data being sent, in which case the send operation provided by the underlying protocol implementation should be used instead of the raw operating system send call.

recv.
The recv operation reads a given number of bytes into a supplied buffer. Unlike the send method, there is only one version of the recv operation. The same caveat of ensuring that the appropriate recv or read operation is used also applies to this method.

start_request.
This method creates the appropriate header to make a request and write it into the provided output CDR stream. This method is closely tied to TAO's GIOP implementation. start_request implementations should essentially be the same in all pluggable protocol implementations. The only thing that should differ is the type of Profile being used. Note that this method is only useful for clients.

start_locate.
This method creates the appropriate header to make a locate request and write it into the provided output CDR stream. This method is closely tied to TAO's GIOP implementation. start_locate implementations should essentially be the same in all pluggable protocol implementations. The only thing that should differ is the type of Profile being used. Note that this method is only useful for clients. Note that this method is only useful for clients.

send_request.
Depending on the concurrency strategy used by the transport it may be required to setup state to receive a reply before the request is sent. Using this method, instead of send, allows the transport (and wait strategy) to take appropiate action. This method is closely tied to TAO's GIOP implementation. send_request implementations should essentially be the same in all pluggable protocol implementations. The only thing that should differ is the type of Profile being used. Note that this method is only useful for clients.

handle_client_input.
handle_client_input reads and handles the reply from the server. It returns zero when there is Short Read on the connection, returns 1 when the full reply is read and handled, and returns -1 on errors. Note that this method is only useful for clients.

register_handler.
This method registers the Connection Handler with the Reactor. It will be called by the Wait Strategy if the Reactor is used for that strategy. This code should essentially be ``boilerplate'' code. It shouldn't differ much between pluggable protocol implementations if the same ACE event handling and dispatching components are used. Note that this method may be deprecated in the future.
There other methods in the TAO_Transport that can be overridden. However, the default implementations should be more than satisfactory for most pluggable protocols.

TAO's existing pluggable protocols implement client-side and server-side specific Transports. For the most part, they can be used as references for other pluggable protocols.

The Connection_Handler

A Connection_Handler is simply a template instantiation of ACE_Svc_Handler<> [1], a service handler. ACE_Svc_Handlers provide a well-defined interface that Acceptor and Connector pattern factories use as their target. Service handlers perform application-specific processing and communicate via the connection established by the Connector and Acceptor components. Typically, TAO Connection_Handlers will subclass ACE_Svc_Handler and do all the interesting work in the subclass.

Connection_Handler Implementation

TAO pluggable transport protocol Connection_Handlers should be derived from the ACE_Svc_Handler class declared in <ace/Svc_Handler.h>. TAO's existing pluggable transport protocol implementations define three classes, a base class derived from an ACE_Svc_Handler specific to that protocol, and a client-side and a server-side class derived from that base class. Generally, it is a good idea to base new Connection_Handler implementations on TAO's existing ones. The interfaces for the existing Connection_Handlers are defined in <tao/IIOP_Connect.h> and <tao/UIOP_Connect.h>.


Notes From a ``Real World'' Pluggable Protocol Implementation

By Bruce Trask <[email protected]>

This section is based on notes I took when adding a different transport layer to the TAO ORB. I was given some initial guidelines on adding an additional protocol and these proved very helpful. Beyond that there was not much more documentation and so I hope the information in this paper will serve to further assist anybody whose is adding a pluggable protocol to the TAO ORB.

I found that in order to successfully add the new protocol capabilities, one had to have a very good understanding of some of the patterns upon which the ACE framework is built. These are the REACTOR, ACCEPTOR, CONNECTOR, FACTORY, STRATEGY, and SERVICE CONFIGURATOR PATTERN. The papers that I found helpful on these were:

Reactor ( PostScript | PDF )
Reactor1-93 ( PostScript | PDF )
Reactor2-93 ( PostScript | PDF )
reactor-rules ( PostScript | PDF )
reactor-siemens ( PostScript | PDF )
Svc-Conf ( PostScript | PDF )
Acc-Con ( PostScript PDF )

These are all readily available from the TAO and ACE website.

My starting point for understanding how to add a pluggable protocol to the TAO ORB came from mailing list entry from Carlos O'Ryan. One can find it in the archives of the comp.soft-sys.ace newsgroup. It is dated 1999/06/02 RE: [ace-users] TAO: ATM pluggable protocol. I will repeat the section of that email that was particularly useful to me. (In the email, he is responding to someone who had inquired about adding the ATM protocol).

Basically, you need to look at the following files:
IIOP_Profile.{h,i,cpp}
IIOP_Connector.{h,i,cpp}
IIOP_Acceptor.{h,i,cpp}
IIOP_Factory.{h,i,cpp}
IIOP_Transport.{h,i,cpp}
Connect.{h,i,cpp} [probably will be renamed IIOP_Connect VSN]

The profile class handles the addressing format for your transport. It would basically be a wrapper around the ACE_ATM_Addr() class. The Connector and Acceptor classes are simply wrappers around ACE_Acceptor<ACE_ATM_ACCEPTOR> and ACE_Connector<ACE_ATM_ACCEPTOR>, again no big deal (I think). The factory is even simpler.

Things get really interesting in the Transport and Connect classes. Transport just implements external polymorphism over the Client_Connection_Handler and the Service_Connection_Handler objects defined in the Connect.{h,i,cpp}, those are simply ACE_Svc_Handler<ACE_ATM_Stream>, but they don't do much work, they just delegate on the Transport classes. This somewhat strange design is easy to understand once you realize that all the ACE_Svc_Handler<> classes are not type compatible (except in their most basic ACE_Event_Handler form). So they must be wrapped using the TAO_Transport class.

Make sure to review ``Pluggable Protocols'' in ``Release Information for the ACE ORB (TAO)'' in the TAO/docs/releasenotes directory.

Just for completeness sake, I'll include some other mailing list entries which were helpful in getting me started. The following is from Ossama Othman.

The stock TAO distribution has support for two transport protocols, TCP/IP and local namespace sockets (aka Unix Domain sockets). However, TAO's pluggable protocols framework allows users to add support for additional transport protocols. All you'd really have to do is implement a SCRAMNet pluggable transport protocol with the interface TAO's pluggable protocol framework provides and you'd be able to use SCRAMNet with TAO just as easily as the IIOP (GIOP over TCP/IP) and UIOP (GIOP over Unix domains sockets) protocols.

The idea is to implement GIOP messaging over a SCRAMNet transport. If you model your implementation on TAO's IIOP and UIOP implementations then it should be fairly straightforward to create a SCRAMNet pluggable protocol for TAO. The hard part is implementing the equivalent ACE classes for SCRAMNet.

. . .

It's actually not that bad. The easiest way to add a pluggable protocol to TAO, IMO, is to base your pluggable protocol on existing ones. As long as you have the same interface for your protocol as the existing ones then it is fairly easy to create your TAO pluggable protocol. However, in order to do that you have to create ACE_SCRAMNet_{Acceptor, Connector, Stream, Addr} implementations, for example, since TAO's existing pluggable protocols use those interfaces.

As long as you use the same interface for your protocol as the interface for ace/ACE_SOCK* and tao/IIOP* then you shouldn't have much of a problem.

This also assumes that you're implementation can satisfy the conditions stated earlier in this document.

Note also that the TAO files pluggable.* are important to review and understand as they contain the abstract classes that form the common inteface for TAO's pluggable protocol framework.

Getting a full understanding on how IIOP was implemented (GIOP over TCP/IP) and also seeing how provisions were made to add UIOP, was very helpful to adding my own protocol. In understanding IIOP, I needed to review the section of the OMG CORBA spec on GIOP, IIOP and Object references and see how this would apply to my protocol.

In my case, I added a transport layer that uses SCRAMNet (from Systran Corp) replicated shared memory hardware. This is actual physical memory cards located on two different machines. When a change is made to one memory then that change appears very quickly (very low latency here) in the other memory. I decided that I would implement GIOP over SCRAMNet as this seemed to be the simplest. With SCRAMNet, one could implement this transport layer for the TAO ORB in a few different ways, GIOP over SCRAMNet, Environment-specific inter-ORB protocol (ESIOP) or using collocation (since it is shared replicated memory). I have not done the latter two, only GIOP over SCRAMNet just to get a proof of concept working.

For a graphical representation of the extensions for the new SCRAMNet classes I have may a skeletal Rose diagram showing (at this point) the inheritance relationships of the new and existing classes. See (TBD) ftp site for this Rose diagram.

The new classes created were.

TAO_SCRAMNet_Profile (Derived from TAO_Profile in Profile.h)
TAO_SCRAMNet_Acceptor (Derived from TAO_Acceptor in Pluggable.h)
TAO_SCRAMNet_Connector (Derived from TAO_Connector in Pluggable.h)
TAO_SCRAMNet_Transport (Derived from TAO_Transport in Pluggable.h)
TAO_SCRAMNet_Server_Transport
TAO_SCRAMNet_Client_Transport
TAO_SCRAMNet_Protocol_Factory (Derived from TAO_Protocol Factory in Protocol_Factory.h)
TAO_SCRAMNet_Handler_Base (as in IIOP_Connect.h)
TAO_SCRAMNet_Client_Connection_Handler
TAO_SCRAMNet_Server_Connection_Handler
ACE_SCRAMNet_Addr
ACE_SCRAMNet_Acceptor
ACE_SCRAMNet_Connector
ACE_SCRAMNet_Stream

I closely followed the way that IIOP and UIOP were defined and implemented in the definition and implementation of the SCRAMNet classes. Following the existing protocol implementation was the largest source of help for me. Being able to step through the operation of the ORB for the IIOP protocol and then transposing that over to my protocol made the process relatively painless and quite the learning experience.

I am using TAO under Phar Lap's Embedded Tool Suite Real-Time Operating System which is an RTOS which supports a subset of the Win32 API and Winsock 1.1. Because of the new SCRAMNet transport hardware I needed to change the ORBs core reactor to a WFMO_Reactor. Any instance of the TAO ORB can only have one reactor type or it won't work. In my case I am using an ORB in one thread that uses the WFMO_Reactor and the SCRAMNet transport, and an ORB in another thread that uses a Select Reactor and the IIOP protocol. I won't go into much of the SCRAMNet specific stuff as I assume most people are interested in adding a pluggable protocol in general.

RE: IORs
I found that I needed to have a full understanding of what the exact contents of a TAO-created IOR were as I needed to be able to understand how to decode the location information that was now written in the IOR with the SCRAMNet specific information. Decoding of the preconnect and endpoint info is important. The endpoint info both in the command line arguments and in the IOR are different for the each protocol and so your implemention of the new classes has to parse this information correctly.

In order to create the ORB with the Win32 Reactor at the core as well as the SCRAMNet protocol factory loaded and initialize I needed to use the svc.conf file with the the following options -ORBReactorType wfmo -ORBProtocolFactory SCRAMNet_Factory

Beyond the above, I just traced through the operation of the IIOP protocol in action to see exactly where I needed to just graft on the new ACE_SCRAMNet classes, the TAO_SCRAMNet classes and their associated implementations for send and recv so that the SCRAMNet hardware was used as the transport and not the ethernet hardware. Questions, comments, changes are welcome. I can be reached at [email protected]


Additional Implementation Information

This section covers additional information not necessarily related to TAO's pluggable protocol framework but may still be of interest to pluggable protocol and ORB developers, nevertheless.

Tags

Tags are used to uniquely identify certain parts of an ORB, including the following:

A list of current OMG assigned tags is available at:

ftp://ftp.omg.org/pub/docs/ptc/99-05-02.txt

For information about tags and tag allocation see:

http://www.omg.org/cgi-bin/doc?ptc/99-02-01

Information about tags used in TAO is available here.


Using a Pluggable Protocol

Once a TAO pluggable protocol is implemented, the ORB is told to load it by adding entries to a Service Configurator configuration file (e.g. svc.conf for that protocol. A typical svc.conf file could contain entries such as the following:

dynamic FOOIOP_Factory Service_Object * TAO_FOO:_make_TAO_FOOIOP_Protocol_Factory() ""
static Resource_Factory "-ORBProtocolFactory FOOIOP_Factory"

These entries would cause a pluggable protocol called ``FOOIOP'' to be loaded into the ORB. By default the IIOP and UIOP (if supported) pluggable protocols are loaded into TAO if no such entries are provided. Explicitly specifying which protocols to load in this way prevents any pluggable protocols from being loaded by default. The -ORBProtocolFactory ORB option causes the specified protocol factory to be loaded into the ORB. Multiple pluggable protocols can be loaded simply by adding more Service Configurator configuration file entries. For example, the following entries would cause the TAO's IIOP and the fictional FOOIOP pluggable protocols to be loaded:

dynamic FOOIOP_Factory Service_Object * TAO_FOO:_make_TAO_FOOIOP_Protocol_Factory() ""
static Resource_Factory "-ORBProtocolFactory FOOIOP_Factory"
dynamic IIOP_Factory Service_Object * TAO:_make_TAO_IIOP_Protocol_Factory() ""  
static Resource_Factory "-ORBProtocolFactory IIOP_Factory"

In this case, TAO's UIOP pluggable protocol would not be loaded. Service Configurator configuration file names are not limited to the name ``svc.conf.'' The ORB can be told to use a configuration file other than ``svc.conf'' by using the -ORBSvcConf ORB option.

Note that the FOOIOP protocol resides in a library other than the TAO library, called ``libTAO_FOO.so'' on UNIX platforms, and ``TAO_FOO.dll'' on Win32 platforms. This ability to dynamically load pluggable protocols in libraries that are completely separate libraries from the TAO library truly makes TAO's pluggable protocol framework ``pluggable.'' Make sure the directory your pluggable protocol library is located in is also in your library search path, typically LD_LIBRARY_PATH on UNIX systems and/or the ``/etc/ld.so.conf'' file on some UNIX systems. Remember to run ldconfig if you modify ``/etc/ld.so.conf.''

Creating an endpoint specific to a given pluggable protocol is simply a matter of using TAO's -ORBEndpoint ORB option. This is detailed in the documentation for the open and open_default methods in the Acceptor section of this document. Once an endpoint is created, the client uses the IOR that points to the object on that endpoint to make requests on that object.

All ORB options are described here.


Bibliography

1
D. C. Schmidt, `` Acceptor and Connector: Design Patterns for Initializing Communication Services,'' in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.

2
C. Cleeland, D. C. Schmidt and T. Harrison, `` External Polymorphism -- An Object Structural Pattern for Transparently Extending Concrete Data Types,'' in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.

3
D. C. Schmidt and T. Suda, `` An Object-Oriented Framework for Dynamically Configuring Extensible Distributed Communication Systems,'' IEE/BCS Distributed Systems Engineering Journal (Special Issue on Configurable Distributed Systems), vol. 2, pp. 280-293, December 1994.

4
H. Hueni, R. Johnson, and R. Engel, ``A Framework for Network Protocol Software,'' in Proceedings of OOPSLA '95, (Austin, Texas), ACM, October 1995.

5
F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal, Pattern-Oriented Software Architecture - A System of Patterns. Wiley and Sons, 1996.

6
Carlos O'Ryan, Fred Kuhns, Douglas C. Schmidt, Ossama Othman, and Jeff Parsons, The Design and Performance of a Pluggable Protocols Framework for Real-time Distributed Object Computing Middleware, Proceedings of the IFIP/ACM Middleware 2000 Conference, Pallisades, New York, April 3-7, 2000.

7
Fred Kuhns, Carlos O'Ryan, Douglas C. Schmidt, Ossama Othman, and Jeff Parsons, The Design and Performance of a Pluggable Protocols Framework for Object Request Broker Middleware, Proceedings of the IFIP Sixth International Workshop on Protocols For High-Speed Networks (PfHSN '99), Salem, MA, August 25--27, 1999.


Footnotes

... abstraction.1
Protocol stacks based on the Internet or ISO OSI reference models are common examples of the Layers architecture.

Ossama Othman
Last modified: Sat Dec 8 10:43:59 PST 2001