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:
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.
(Server/Console) |
(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()
|
|
ServiceNode.CommandReceived()
|
*Note: A service node factory needs to be created only if you are operating in a JVM other than the IBM Director Management Console.
This section describes the programming steps required to initiate communication from a service requester to a service provider.
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:
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);
// Create a new service node
srvTask.svcNode = new ServiceNode("ServerTask");
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:
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);
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"Note: TCPIP host name resolution is not supported.
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.
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.
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);
int NumInputParms();
int NumOutputParms();
byte[] OutputParm(); byte[] OutputParm(int index);
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.");
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_SERVICEFAILEDExample 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())); } }
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.
This section describes the programming steps required by a service provider to respond to a request from a service requester.
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);
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. ... }
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.
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.
In C++:
In Java:
In C++:
In Java: 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.
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. Gets a pointer to the command's input parameter buffer specified
by index. Parameters are numbered using 0-based indexes.
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.
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:
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.
This section describes programming considerations for initiating commands
from an IBM Director managed system (agent) and for security-related issues.
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 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().
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++:
In Java:
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:
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:
Note: Positive session wait values are currently not used; therefore
the session wait function is implemented but not tested.
Note: Holding a session does not preclude the session from being
deleted by the IPC when the session inactivity timer expires.
The get method to retrieve the current value is SessionHold().
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):
ULONG NumInputParms();
int NumInputParms();
ULONG NumOutputParms();
int NumOutputParms();
void AddOutputParm(PVOID data, ULONG size);
In Java:
void AddOutputParm(byte[] data);
void AddOutputParm(byte[] data, int start, int length);
PVOID InputParm(ULONG index);
byte[] InputParm();
byte[] InputParm(int index);
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
Advanced features
Postponing a Command reply
Command timeout
Agent-initiated commands
virtual SendAuthorizationCommand(ULONG time_out);
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