IBM Director Interprocess Communication

This topic describes the concepts and programming requirements for communicating among IBM Director extensions. If you are creating a new server task, agent service, or console extension to the IBM Director GUI, read this section before you begin.

This section includes the following topics:

Related topics

Related sample code

How Director implements IPC

For a high level description of Director IPC, click here.

IBM Director IPC is implemented through service nodes. A service node encapsulates communication in an object-oriented fashion, hiding transport details with the ServiceNode and Command classes. The ServiceNode class must be used for communication between:

The Command class is the means for carrying IPC commands across a service node. The server and the agent each create their own service node. IPC commands are sent by the server to the agent to initiate service requests for the user. These commands are sent using methods provided by the server's service node and routed for processing to a command processor associated with the agent's service node. A command reply is returned to the server upon completion of the request.

The IBM Director Server IPC might be implemented using Java or C++ classes; the IBM Director Agent IPC is implemented using C++ classes. These classes use a request/response message-passing model for local and remote communications. Because transport details are hidden, the remote IPC is protocol-independent at the application level and uses the same class interface as the local IPC. The TCP/IP, IPX, and NetBIOS communication protocols are supported within this common interface.

In this section, examples of service node communication are in the context of a service requester (server task) and a service provider (agent service). However, service node communication is not limited to server task-to-agent service communication; for example, a server task might need to make requests to another server task. Refer to the File explorer sample overview for sample code that implements service node communication. The following table summarizes the process flow for communication between a simple server task and an agent.


 
Service Requester
(Server/Console)
 
Service Provider
(Agent)
Create ServiceNodeFactory *    
Create ServiceNode   Create ServiceNode
Create IPC command: 
fill in data (input parameters) and destination
   
Send it, ServiceNode.SendCommand()
------>

Wait for commands: ServiceNode.ProcessCommands()
CommandComplete() 
  • Process return code 
  • Process optional output  parameters 
  <------ 
ServiceNode.CommandReceived() 
  • Process command 
  • Load output parameters if needed 
  • Set return code 
  •  Return TRUE 

*Note: A service node factory needs to be created only if you are operating in a JVM other than the IBM Director Management Console.

Service requester (Server task or console) actions

This section describes the programming steps required to initiate communication from a service requester to a service provider.

Creating a ServiceNodeFactory

In Java, service nodes are implemented to locally access the IBM Director transport. A service node factory must be created before a serviceNode can be instantiated. To create a service node factory, use class ServiceNodeLocalImplFactory.

Example:

// Service node will be created from local implementation factory.
Class cls = Class.forName("com.tivoli.twg.libs.ServiceNodeLocalImplFactory");
// Create a new instance of the service node implementation factory class.
ServiceNodeImplFactory fct = (ServiceNodeImplFactory) cls.newInstance();
// Set the service node factory instance in the service node class.
ServiceNode.SetServiceNodeFactory(fct);

Notes:

Creating a ServiceNode

Service nodes are implemented using the ServiceNode class. A service node is an endpoint for communication between two processes. The primary job of the service node is to send and receive commands for processing. Typically, the service requester component sends commands to the service provider (agent), but does not receive commands.

Several constructors are used for Java-based service nodes. Refer to com.tivoli.twg.libs.ServiceNode  for information on constructors not listed here. Commonly used service node constructors have the following signature:

ServiceNode(String sn_name);
ServiceNode(String sn_name, boolean make_thrd);
sn_name
Specifies the unique name that identifies the service node on the system where it is created.
make_thrd
Specifies that a separate thread can be created for processing commands. If TRUE, commands are processed in a separate thread. If FALSE, command processing is executed in the caller's own thread within the ProcessCommands() method. If the caller's own thread has no other processing to do, setting make_thrd to FALSE eliminates the overhead of having another thread.
Example:

// Create a new service node
srvTask.svcNode = new ServiceNode("ServerTask");

Building an IPC Command

The IPC Command class is used to encapsulate the data transferred between service nodes. Often you will want to know when the recipient of a command has finished processing a command.  When you need to be informed when a command is complete you can either create a subclass of the Command class and override the CommandComplete() method, or have a class of your choosing implement the CommandCompleteListener interface (this is described later in Processing the results).

You must specify two pieces of information to send a command:

  1. The command code that must be specified when you create the command object
  2. The destination address of the command recipient.

    Creating an IPC Command

    The syntax for instantiating a Command is:

    Command(long cmd);

    where cmd is a unique command code used by the recipient to distinguish between incoming commands.

    Example:

    // Create a new command with command code 1911.
    Command cmd = new Command(0x777);

    Setting the Command's destination address

    The destination address for the command is set using the SetDestinationAddress() method. The syntax for the command is:

    cmd.SetDestinationAddress(address);

    where address is of type String and has the form "protocol::proto_addr::servicenodename"
    protocol
    Can be either TCPIP, IPX, or NETBIOS
    proto_addr
    The appropriate address for the protocol, for example, dotted-decimal format for a TCP/IP address and 1-12 character format for a NetBIOS address.

    Note:  TCPIP host name resolution is not supported.

    servicenodename
    The name of the destination service node.

    Example:

    // Set the message destination
    logCmd.SetDestinationAddress("TCPIP::127.0.0.1::BobSN");

    The example above demonstrates how you need to specify a destination address when sending a command to an IBM Director agent. When you send commands between your task's GUI and your task's server or from your task's server to another IBM Director server, you need only specify a service node name as the destination address to use. In the example above, you would only use "BobSN" as the destination address. Service nodes that are created on the server and on the IBM Director Console can be viewed as being in the same logical address. IBM Director knows which protocol and which address to use when routing commands for non-agent IPC.

    Specifying input and output parameters on IPC commands

    The command code is used to uniquely identify a command, but you might want to send data along with the command that is necessary for performing some operation by the recipient. This is accomplished by adding input parameters to a command prior to sending it. Commands can have any number of input and output parameters. Input parameters are:

    After the recipient has handled a command, you might want to return results to the sender. This is accomplished by adding output parameters to a command. Output parameters are:

    The Command class has several methods for working with input and output parameters. This section describes some of the frequently used methods. Refer to com.tivoli.twg.libs.Command for complete information on the Java methods for working with input and output parameters.

    AddInputParm()
    Adds an input parameter to the command. Typically, you will use the AddInputParm() method to pass data to the service provider (agent). This method is passed a reference to a byte array containing the input data or it is passed a reference to a byte array containing the input data, start offset into the buffer, and the buffer size in bytes.

    Note:  When communicating with C++ service nodes, IntelByteBuffer must be used for command input and output parameters. IntelByteBuffer is a class for creating or interpreting byte arrays representing Intel byte order structures with C-type data types. See com.tivoli.twg.libs.IntelByteBuffer for more details. If your sending and receiving service nodes are both implemented in Java, IntelByteBuffer is still a good way to flatten objects for command input and output parameters. However, you can create the byte array in any way you wish. For instance, you can use Java's built-in serialization mechanism to add a Java object to a command.

    The syntax of the AddInputParm() method is:

    void AddInputParm(byte[] data);
    void AddInputParm(byte[] data, int start, int length);

    Example:

    
    Command cmd;
    .
    .
    .
    String msg = "Hello world";
    
    byte[] msgBB = new byte[IntelByteBuffer.GetUTF8Length(msg)];
    IntelByteBuffer.WriteUTF8(msgBB, msg, 0);
    
    cmd.AddInputParm(msgBB);
    
    NumInputParms()
    Gets a count of the number of input parameters in the command.

    int NumInputParms();
    NumOutputParms
    Gets a count of the number of output parameters in the command.

    int NumOutputParms();
    OutputParm
    Gets a byte array reference to the command's next output parameter (auto-incremented index). Gets a byte array reference to the command's output parameter buffer specified by index. Parameters are numbered using 0-based indexes.

    byte[] OutputParm();
    byte[] OutputParm(int index);

    Sending the Command

    Commands are sent using service node methods. IBM Director's IPC mechanism routes the command to the receiving node's CommandReceived() method. When the service provider's CommandReceived() method returns "true," the IPC routes a command reply back to the CommandComplete() method associated with the service requester's Command class and to any object that has registered itself as a command complete listener for the command.

    Two methods, SendCommand() and SendAsynchCommand(), are provided by the com.tivoli.twg.libs.ServiceNode class to send commands. Both methods route the command to the service provider's CommandReceived() method and invoke the command's CommandComplete() method when the reply is returned.

    The SendCommand() method executes in the caller's thread and, therefore, blocks until the command's CommandComplete() method returns and any command complete listeners have returned. The SendAsynchCommand() method returns immediately and sends the command asynchronously, allowing the calling thread to continue. The command's CommandComplete() method is called after the service provider has finished processing the command.

    Both SendCommand() and SendAsynchCommand() take a reference to a command object instance as a parameter. References to the command object are dropped after the CommandComplete() notification is finished. Garbage collection can then occur on the object. Therefore, do not reuse command objects in Java; instantiate a new Command object for each command sent.

    In cases where either method can be used, SendAsynchCommand() is the recommended method because the sending process can continue processing asynchronously with command processing in the receiver.

    The destination address for an IPC command is set before the command is sent using the SetDestinationAddress() command class method. The command's return address is built node-by-node during the send process. On arrival at the destination, the command's destination and return addresses are swapped in preparation for sending the command reply back to the sender.

    Example of synchronous:

    // Send the command synchronously.
    if (!srvTask.svcNode.SendCommand(logCmd)) 
       System.out.println("SendCommand failed.");
    Example of asynchronous:

    // Send the command asynchronously.
    if (!srvTask.svcNode.SendAsynchCommand(logCmd)) 
       System.out.println("SendCommand failed.");

    Processing the results

    If you want to be notified when the recipient of a command has finished processing a command, you can derive a class from the Java com.tivoli.twg.libs.Command class.  The derived class should implement the CommandComplete() method for processing the Command reply. CommandComplete() has the following declaration:

    public void CommandComplete();

    This method is invoked in the service requester when the command has completed. Subclassing Command to receive CommandComplete() notifications is the recommended technique if minimal processing occurs on CommandComplete().

    As an alternative to subclassing Command class, you can implement the com.tivoli.twg.libs.CommandCompleteListener interface. By implementing this interface, an object can be defined which can be registered as a command complete listener for a command. The CommandCompleteListener interface defines a single method: CommandComplete( Command ). The command object passed to this method is a reference to the command object which has completed processing. Using the CommandCompleteListener interface is the preferred way of command complete notification if a large amount of processing occurs on CommandComplete().

    There are situations where a command you have sent might not reach the intended recipient. In these cases, the command complete processing will still be performed for a command. To determine if a command you have sent failed to reach the intended recipient, you should check the command's return code in your CommandComplete method. The method for checking a command's return code is:

    long ReturnCode();

    Some error conditions prevent a command from ever reaching the target service node. The following standard command return codes are declared static final long in Command.java:

    CMDRET_SEND_FAILED
    CMDRET_SECURE_FAIL
    CMDRET_SEND_TIMEOUT
    CMDRET_SERVICEFAILED
    Example of a minimal CommandComplete() method:

      public void CommandComplete() {
          // Zero return code means success.
          if (ReturnCode() == 0) {
             System.out.println("Req: Message logged");
          }
          else {
             System.out.println("Req: Message log request failed.  RC = "+
                                Long.toHexString(ReturnCode()));
          }
       }

    Putting it all together

    The msglogr sample code illustrates the creation of a service node for an agent-like program that processes one command. This service logs a text message from the requester task along with a message severity that indicates the urgency of the message. The message text and severity are input parameters passed with the command from the requester.

    First, a new class is derived from the service node class so that a command processor can be defined for the service node. The class is declared in the msglogr.hpp file.

    Next, the service's command processor and main program are defined in the msglogr.cpp file.

    In the following sample code, a text message and severity are sent to msglogr. This command line requester uses ServiceNodeLocalImplFactory to create a service node because it is a server task that runs in its own JVM.

    Refer to SrvTask.java for a complete example of a service requester that sends synchronous commands and implements the CommandCompleteListener interface.

    Refer to SrvTaskAsynch.java for a complete example of a service requester that sends asynchronous commands and uses the LogMsgCommand  subclass of Command.

    Note:  The msglogr program must be running on the destination system before you can execute these Java samples. You must also run these samples on an IBM Director server that has the IPC started.

    Service provider (Agent) actions

    This section describes the programming steps required by a service provider to respond to a request from a service requester.

    Creating a ServiceNode

    Service nodes are implemented using the ServiceNode class. A service node is an endpoint for communication between two processes. The primary job of the service node is to send and receive commands for processing. Typically, the service requester component sends commands to the service provider (agent), but does not receive commands.

    The constructor for service nodes has the following signature:

    ServiceNode(char *name, BOOL rd_thrd=TRUE);
    name
    Specifies the unique name that identifies the service node on the system where it is created.
    rd_thrd
    Specifies that a separate thread can be created for processing commands. If TRUE, commands are processed in a separate thread. If FALSE, command processing is executed in the caller's own thread within the ProcessCommands() method. If the caller's own thread has no other processing to do, setting rd_thrd to FALSE eliminates the overhead of having another thread.

    The following is a code example to illustrate creating a service node in C++. This example uses the Create() method which returns TRUE if the service node was created successfully.

    class MsgSvcNode : public ServiceNode
    {
       public:
          MsgSvcNode() : ServiceNode("Provider", FALSE)
             {};
        ...
    }
    void __cdecl main()
    {
       MsgSvcNode *pMsgSvcNode = new MsgSvcNode();
       // Test if service node created.
       if (!pMsgSvcNode-Create())
          // Error.
       ...
    }

    Waiting for Commands

    Service providers (agents) are generally command-driven. The process starts, initializes, and then waits for commands from service requesters. To enter the wait-for-commands loop, you must invoke the ProcessCommands() method of the ServiceNode class. For example, the service node in the previous example is created with command processing performed in the caller's own thread. This requires that the main program call the ProcessCommands() method as shown below:

    pMsgSvcNode-ProcessCommands();
    To exit the ProcessCommands loop, the CommandReceived method (described below) must return FALSE.

    Processing Commands

    To enable command processing in the service provider (agent), a ServiceNode subclass and a command processor are defined. Commands received from other processes are routed to the provider's service node CommandReceived() method, which is an overridden method of the ServiceNode class. Supported commands are processed by the CommandReceived() method.

    The CommandReceived() method has the following declaration:

    In C++:

    virtual BOOL CommandReceived(Command &cmd);
    In Java:

    public boolean CommandReceived(Command cmd);
    The command from the requester is passed as a parameter.

    The command class has several methods for working with input and output parameters. This section describes some of the frequently used methods. Refer to for complete information on the C++ methods.

    NumInputParms()
    Gets a count of the number of input parameters in the command.

    In C++:

    ULONG NumInputParms();

    In Java:

    int NumInputParms();
    NumOutputParms()
    Gets a count of the number of output parameters in the command.

    In C++:

    ULONG NumOutputParms();

    In Java:

    int NumOutputParms();
    AddOutputParm()
    In C++:

    Adds an output parameter to the command. This method is passed a pointer to a data buffer containing the output data and the size of the buffer, in bytes.

    void AddOutputParm(PVOID data, ULONG size);
    In Java:

    Adds an output parameter to the command. The method is passed a reference to a byte array containing the output data or it is passed a reference to a byte array containing the output data, the start offset into the buffer, and the buffer size in bytes.

    void AddOutputParm(byte[] data);
    void AddOutputParm(byte[] data, int start, int length);
    InputParm()
    In C++:

    Gets a pointer to the command's input parameter buffer specified by index. Parameters are numbered using 0-based indexes.

    PVOID InputParm(ULONG index);

    In Java:

    Gets a byte array reference to the command's next input parameter (auto-incremented index). Gets a byte array reference to the command's input parameter buffer specified by index. Parameters are numbered using 0-based indexes.

    byte[] InputParm();
    byte[] InputParm(int index);

    After processing a command, CommandReceived() sets a return code in the command using the SetReturnCode() method of the Command class. The CommandReceived() method returns TRUE to indicate the service node should continue processing, or returns FALSE to indicate the service node should be closed.

    Example:

    BOOL MsgSvcNode::CommandReceived(Command &cmd)
    {
       // Vars for the input parameters
       unsigned short severity;
       char * message;
    
       cout << "Msg received..." << endl;
       switch (cmd.CommandCode()) {
    
       // Command code for synchronously sent commands.
       case LOG_MSG:
          // Retrieve the input parameters
          severity = *(unsigned short *) cmd.InputParm(0);
          message = (char *) cmd.InputParm(1);
          // Log the message
          stream = fopen("MESSAGE.LOG", "a+"); // open the log file for append
          fprintf(stream, "Severity %d message from %s: %s\n", severity, 
                                                               cmd.DestinationAddress(),
                                                               message);
          fclose(stream); // close the log file
          cmd.SetReturnCode(0);
          break;
    
       // Command code for asynchronously sent commands.
       case LOG_MSG_ASYNCH:
          // Retrieve the input parameters
          severity = *(unsigned short *) cmd.InputParm(0);
          message = (char *) cmd.InputParm(1);
          // Log the message
          stream = fopen("MESSAGE.LOG", "a+"); // open the log file for append
          fprintf(stream, "Severity %d asynchronous message from %s: %s\n", severity, 
                                                                            cmd.DestinationAddress(),
                                                                            message);
          fclose(stream); // close the log file
          cmd.SetReturnCode(0);
          break;
    
       // All other commands.
       default:
          cmd.SetReturnCode(1);
          break;
       }
       return TRUE;
    }

    Putting it all together

    The msglogr sample code illustrates the creation of a service node for an agent-like program that processes one command. This service logs a text message from the requester task along with a message severity that indicates the urgency of the message. The message text and severity are input parameters passed with the command from the requester.

    First, a new class is derived from the service node class so that a command processor can be defined for the service node. The class is declared in the msglogr.hpp file.

    Next, the service's command processor and main program are defined in the msglogr.cpp file. Note that the service node is created so that the command processing is done on the caller's own thread, which requires the main program to call the ProcessCommands() method. This program also uses the Create() method which returns TRUE if the service node was created successfully.

    Note:  msglogr must be run on an IBM Director managed system that the server programs have authorization to communicate with. The IPC service must also be running on the managed system.

    Advanced features

    This section describes programming considerations for initiating commands from an IBM Director managed system (agent) and for security-related issues.

    Postponing a Command reply

    Commands are generally processed within the context for the recipient's CommandReceived method. Once this method returns true, the command complete listeners for the command are called. There are times when you might want to defer command complete processing so that you can handle command processing outside of the scope of the CommandReceived method. This is accomplished by calling the command method PostponeReply(). By postponing a command reply, the command-complete listeners are not called until the postponed reply is sent by the receiving service node (or the command times out, even if the command received function has returned.

    One situation where this is useful is when you want to process a command asynchronously on another thread. Within the CommandRecieved method you can call PostponeReply on the command object. You can then process the command on another thread. When command processing is complete, call SendPostponedReply() on the receiver's service node and pass the command object whose reply was postponed. Calling SendPostponedReply causes the command complete listeners to be called.

    Command timeout

    Command objects have the concept of a timeout value that is used to represent a reasonable amount of time it takes to perform the operation represented by a command. Director's default command timeout value is 15 seconds. The timeout value represents the time elapsed from the moment the command is sent to time the command is completed.

    If a receiving service node is processing a command and the command's timeout value has elapsed, the command complete processing occurs immediately for that command and the command's return code is set to CMDRET_SEND_TIMEOUT. Postponing a command reply has no effect on the timeout processing for the command. You can change a command's timeout value using the Command method SetTimeOut().

    Agent-initiated commands

    Commands are generally sent from the server to the agent, but not from the agent to the server. For most services, this kind of request/response relationship is sufficient. However, in cases where it is necessary for an agent to initiate commands to a server, the security mechanisms on the two systems can cause problems. Normally, security is set up to allow remote access to a system's services only for trusted managers. This relationship is not mutual; an IBM Director Server is not set up, normally, to allow remote access to its services.

    One way to circumvent this problem is to use the SendAuthorizationCommand, a special command for authorizing an application to send messages (assuming the application is on a system that is not authorized to send). This command temporarily authorizes a system to send commands to another system.

    In C++:

    virtual SendAuthorizationCommand(ULONG time_out);

    In Java:

    public SendAuthorizationCommand(int timeout);
    The sending system is authorized to send further commands until the timeout is reached. If a timeout of zero is specified, an outstanding authorization is canceled.

    Specifying the transport protocol through session support

    An application that needs to send large amounts of data between the server/console and one or more agents might want to use session support. Session support enables you to specify (but does not ensure) that a protocol-specific session will be used for communications. Session support is not recommended for transferring small quantities of data or for intermittent agent communications. Currently, session support is only implemented for the TCP/IP protocol; however, performance is not degraded by specifying sessions over protocols that are not supported. Therefore, you can write your program to take advantage of session support and bypass the need to change your code later, if other protocols are supported.

    Note:  Session support through a firewall is not currently supported.

    For each command instance, the service node user (for example, server task) can select from two IPC transport modes:

    SESSION_NONE (default)
    The command is transported using the standard, connectionless IPC transport.
    SESSION_PREFERRED
    The command is transported using session mode if a session can be established with the service node target system. If a session cannot be established, standard connectionless transport is used.

    Use the SetSessionMode() method of the com.tivoli.twg.libs.Command class to set session mode.

    When a session mode (preferred or required) is selected for the command, additional transport flags can be set to further qualify the session use policy in effect:

    The following new command return codes defined in class com.tivoli.twg.libs.Command are used by IPC to report errors unique to the session transport mode. These return codes are possible only in session required mode (not in session preferred mode):

    CMDRET_SESS_DISABLED_LCL
    The network protocol on the local system is not IPC session capable.
    CMDRET_SESS_DISABLED_RMT
    The network protocol on the remote system is not IPC session capable.
    CMDRET_SESS_UNAVAIL_LCL
    The network protocol on the local system has reached the IPC session limit.
    CMDRET_SESS_UNAVAIL_RMT
    The network protocol on the remote system has reached the IPC session limit.
    CMDRET_SESS_WAIT_EXPIRED
    The time specified for the command to wait for a session has expired.