Tivoli Service Desk 6.0 Developer's Toolkit Script Programming Guide
This chapter is designed to introduce the fundamental concepts that are necessary to
use the TSD Script networking extension statements to communicate across your network.
This chapter also describes special networking concepts and TSD Script networking
statements and constants.
These TSD Script statements may be used to customize the source code for your installed
applications or to create your own custom TSD Script distributed application.
Although this chapter touches on some fundamental aspects of the TSD Script language and networking principles, it is assumed that you are familiar with both areas. If you are modifying an application, you may want to work with someone who has customized other applications or who has used TSD Script and Developer's Toolkit.
The statements included with the TSD Script language allow for two distinctive types of networking architectures:
In this chapter, the description of the TSD Script networking services is generally applicable to both architectures. Where differences exist between the architectures, they are stated.
There are many terms used to describe the interactions between computers on a network. Although you may be familiar with some or all of these terms, they are defined here specifically in the context of their use with the TSD Script networking extensions.
Bi-directional communication occurs when two machines each play the roles of both client and server. In other words, Machine A (client) can request Machine B (server) to perform some service. Likewise, Machine B can switch roles and become the clientby requesting Machine A to perform some service. See also the definition of reverse connection. See peer-to-peer.
A client is an application that requests a service from a server. An example of a client is a workstation on a network connecting to a file server. The connection is initiated by the client.
A connection is the logical link between a client and a server.
An event handler is a special TSD Script function designed to process asynchronous events. Event handlers run on the server to process network requests for a service.
Each connection has a handle. The handle contains the information about the client machine and the service that the server is providing.
An TSD Script server is an TSD Script process running on a networked computer that provides a service.
The local event handler is the event handler operating on the local machine. If this event handler is servicing requests from a different machine, the machine is a server.
The NETx statements, or extensions, are the TSD Script statements that begin with the prefix, NET. These statements were created to allow TSD Script applications to communicate across a network.
A peer-to-peer environment is one in which a single machine may play the role of both a client and a server.
Registering an event handler associates the handler with an event source.
A reverse connection can be established in a peer-to-peer environment when a server requests a client to perform some serviceeffectively switching the roles of client and server. To handle this communication, a reverse connection is established between the local and the remote server.
A server is a machine that provides a service to a client. An example of a server is a networked printer.
A service is the function performed by the server. If a printer is a server, the service it provides is printing.
Event handlers are used throughout TSD Script and Developer's Toolkit to support an
event-driven environment. In an event-driven environment, an event (such as a keystroke, a
mouse click, or a network message) triggers a response by the application (such as opening
a new dialog box or selecting an entry in a field). To process events, each application
uses event handlers designed specifically for them.
Applications that use event handlers do not have to ask or "poll" for network
events. Normally, event handlers "sleep" (aren't active) until an event occurs,
then process a request for service.
A single server can have multiple event handlers. Each event handler is designed to
provide a specific service, or set of related services, as explained later in this
chapter.
Event handlers are defined in the routines section of an TSD Script program. Network
event handlers must be declared as type NETCONNECTION.
This event handler type is different from the default type, WINDOW. Since the type
determines the $Handle that is used, only event handles of type NETCONNECTION
can be used with the NETx statements.
NETCONNECTION EVENT TalkConnectEvent( REF whdl: WINDOW ) IS
When you define an event handler, you must also register its service, as described in
the following sections. Registering an event handler associates the handler with an event
source.
There are two different statements that you can use to register an event handler:
Event handlers registered with NetRegister are called NetRegister event handlers. Event handlers registered with NetListen are called NetListen event handlers.
This chapter focuses on the use of NetRegister, which is used frequently for "standard" event handlers. For customizing event handlers, see the "Advanced Statements" section in this chapter.
You must register an event handler with the server for it to receive and process requests for service. To register an event handler with the NetRegister statement, use the following syntax:
NetRegister( TalkConnectEvent, 'Talk' );
When you register an event handler, you specify the name of the service it provides. A service is a category of actions performed by the event handler.
In the following example, the name of the service defined for the TalkConnectEvent network event handler is Talk.
NETCONNECTION EVENT TalkConnectEvent( REF whdl: WINDOW ) IS ACTIONS . .. END; NetRegister (TalkConnectEvent, 'Talk');
In addition to the services noted previously, a port number extension is supported that specifies an IP port. The port number controls:
The default port is 5001, and the default service is asenet/tcp. If the service is in the services file, asenet/tcp is used instead of the default number.
A port number may be specified in the command prompt with the following command:
/p=nnn
A port number issued from a command prompt overrides both the default port and the default service.
Note: Port numbers must be the same for the client and the server.
You can create an event handler with a service defined as either a zero-length string
('') or $Unknown. This type of service is called a wildcard service.
When a wildcard service is defined on the server, it services any request specified by the
client for which the server cannot find a matching service name.
You can also define a wildcard service on the client. In this case, the server must
have a service with the service name of either '' or $Unknown.
Note: You can define a wildcard service only when registering the event
handler with the NetRegister statement. (NetListen
always uses a wildcard service.)
When you register an event handler, a template for the service is defined on the server. This template includes information from both the definition of the event handler in the routines section of the code as well as information specified when the event handler was registered.
The template is held in storage until a connection is requested. Then the information
in the template is used to instantiate the service for the connection.
There are two basic types of changes that you can make to a template:
Any time you need to make changes to a template, you can either re-register the event handler or register its service. When you re-register an event handler, the new parameter values are implemented immediately. All new connections opened for the template will use new values. Existing connections are not affected by the change(s) to the template.
Caution: You must specify the service name exactly as it is specified in the original template. Otherwise, the NetRegister statement creates a new template for the event handler, and the existing template will not be modified.
Once a template is created, it becomes immediately and continuously receptive to requests for service. However, a template can be stopped from responding to requests for service at any time. To do this, you must re-register the service with the special event handler $NullHandler:
NetRegister ($Nullhandler, 'service');
In this syntax, "service" is the name of the service associated with the event handler you want to stop.
After you re-register the service with $NullHandler, future requests for service from the template are denied, but any existing connections that use the template are not affected.
If you need to restart the server, you must re-register the event handler and its service.
An event handler may service connections with many different clients. It may be difficult, simply by looking at the connection handle, to determine the service that is being performed.
Likewise, it may be difficult to determine which server is performing the service or which client is being served. This is especially true for a NetListen event handler, which responds to every service request it receives and which shares its instance data.
TSD Script provides two statements that can be used to obtain information about the host (server) and service from a connection handle:
Because TSD Script supports a client/server architecture as well as a peer-to-peer architecture, the NetGetHostName statement is designed to get information about the remote machine, regardless of whether or not it is playing the role of the server.
If you call the NetGetHostName statement on the client, you will get information about the server. If you call the NetGetHostName statement on the server, you will get the client's name.
There are probably many NETCONNECTION event handlers on any server. Occasionally, you may need to pass messages between them. Because it is not efficient to send these messages across the network, Developer's Toolkit provides the statement NetLoopBack. NetLoopBack allows you to send a message to a network event handler from your local machine.
TSD Script networking applications use a connection-based protocol. Interaction between
machines on a network occurs through a connection. For a server to provide a requested
service to a client, a connection must be opened.
There are three elements in every connection:
It is the combination of these three elements that makes a connection unique. The
process of opening and closing a connection involves each of these elements.
Multiple connections can be open between a client and a server. However, a client can have
only one connection open for a specific service on a server/port.
Note: If a connection is already open between a client and a specific
event handler on a server, requesting a specific service returns the handle of the
existing connection.
After you register an event handler and its associated service, the server is ready to receive requests for the specified service.
This section describes the process of opening a connection and lists the steps for the procedure used to open a connection.
The procedures to open a connection and an explanation of each step follows.
When a client requests a service, the client must specify the exact name of the service that it needs. (The name of the service is defined when the event handler is registered on the server.) There is no comprehensive list of the available services provided by a server and service names are not case-sensitive.
A port can be specified as part of the service string. If no port is specified, the default is used.
A client can request a wildcard service by specifying either a zero-length string or $Unknown. In this case, the server must have a service with the service name of either NetListen, or $Unknown to fulfill the request.
Note: If the client requests a service that is not defined on the server, and if there is a wildcard service or NetListen is defined, the server matches the client's request with the wildcard service.
If the client cannot communicate with the server, a HOST_UNREACHABLE error code (-13) is returned to the client.
If a TSD Script server is not running, a -13 HOST_UNREACHABLE error code is returned to the client.
If the connection has already been established, TRUE is returned to the client. The handle for the connection is also returned.
If a matching service name is found, the server returns a handle to the client. The handle points to a copy of the template that provides the service. When the client receives the handle, it can continue with its processing.
If the server finds a wildcard service, the server returns the handle to the client. The handle points to a copy of the template that provides the wildcard service. When the client receives the handle, it can continue with its processing.
If a NetListen event handler is found, a handle is returned to the client that describes the connection to the client machine. If found, the handle points directly to the NetListen event handler. When the client receives the handle, it continues processing event data.
If a NetListen event handler is found, go to step 10. If a NetListen event handle is not found, proceed to step 8.
Once $MsgNetConnect is processed on the server, the connection is open and the service can be used.
The process of closing a connection is usually initiated by the client. However, the server can also initiate the closure. In either case, the NetClose statement is used to close the connection.
Note: If either the client or the server goes down, the connection between them is dissolved. To re-establish the communication between the machines, you must reopen the connection.
When a client closes a connection, the following process occurs:
Note: If the event handler is a NetListen event handler, there is no instance data specifically defined for the connection. The closure process is completed when the handle is marked as closed. No $MsgNetDestroy message is sent.
If the client or server program end, or if there is a break in the network, steps 2 and 3 still occur.
If the server initiates the closure of the connection, no communication exists from the server back to the client.
The next time the client attempts to communicate with the server, it detects the
closure and generates an error message such as "invalid handle."
In a peer-to-peer environment NetClose closes both the client and server
connection.
Instance data is user-defined TSD Script data that is associated with a connection. Instance data is created when the connection is created and is destroyed when the connection is closed.
The event handler receives a reference to the instance data with every message for the connection. The instance data stores information specific to the connection.
When you register an event handler with NetRegister, you can specify an initial value for the instance data of that event handler. All connections that are opened for the template receive a copy of the instance data initialized to the value you specified, or to $Unknown if no initial value is provided.
When you specify the instance data, remember the following.
For example:
connectionData is Record callCount = INTEGER; . . . END;
NETCONNECTION EVENT MyEvent (REF data: ConnectionData); ACTIONS When $Event IS $MsgCreate Then data.callCount=1; . . . END; END; StmtData : ConnectionData; NetRegister( MyEvent {StartDate}, 'Service');
When a connection is opened, the instance data defined for the event handler is instantiated in the copy of the template for the connection and is assigned its initial value. This process is called initialization.
If you set the value of the instance data in the template to $Unknown, this means that initialization does not occur.
Initialization also does not occur unless you set a value for the initial value of the instance data. If you do not set a value, the initial value is assumed to be $Unknown.
There may be multiple connections open for a single NetRegister event handler. Each of these connections has its own copy of the instance data.
A client can send a request to a server at any time, regardless of whether the server is currently in the process of handling a previous request or not. Depending on how quickly the client requires a response from the server, and whether the client requires data to be returned from the server, it may send its request with either a blocking or non-blocking statement:
When a server receives requests, it adds them to its queue. As it is able, the server processes the requests in the order in which they were received. (In some cases, the server may place blocking messages in the queue in front of non-blocking messages). Depending on the condition of the network, it may take some time before the server can service a request.
If the statement sent to the server is a blocking statement, no information is returned
to the client until the entire process can be completed on the server.
SendMessage is the only blocking statement in TSD Script.
To prevent deadlock between a client and server, only one SendMessage can
be active on the connection at any one time.
If the statement sent to the server is a non-blocking statement, the server returns a reply to the client which effectively says, "I have your request" when the statement is added to the queue. The client can then continue its processing.
There are several non-blocking statements:
You can choose either blocking or non-blocking statements to communicate with the server. Choosing either type has some risks that you need to be aware of:
In TSD Script and Developer's Toolkit, handles are used extensively to keep track of window and network activity.
Handles are required for connections with either the SendMessage or PostMessage statements. These statements are blocking and non-blocking, respectively. This characteristic determines the behavior of the client and the server.
The server opens a handle when it locates the event handler template with the service that matches the service requested by the client. The handle is contained in the $Handle parameter and passed to the event group servicing the connection.
The server marks a handle as closed when the server closes its end of the connection. If you attempt to access a closed connection, you receive an error message (Invalid_Handle).
The TSD Script NETx statements can be used in both client/server and peer-to-peer architectures. To handle these different architectures, there are two formats for the NetConnect statement.
In the client/server architecture, the client must specify a host name and service so that the server can respond to the client's request and return the service so that the server can respond to the client's request and return the requested service.
FUNCTION NetConnect(REF hndlHost: NETCONNECTION, .VAL hostName: STRING, . VAL service: STRING .): INTEGER;
To respond to the client in peer-to-peer architecture, the server must open a return connection. The peer-to-peer format is really a shortcut for creating a return connection. This format is optional; you may achieve the same result by using the client/server format.
FUNCTION NetConnect( VAL hndlHost: NETCONNECTION ): INTEGER;
You need only one handle for a peer-to-peer connection. The handle will contain all of the data about the connection.
For a detailed description of NetGetHostName or NetGetService, see the next chapter.
Use the $Handle constant to send a reply to a remote server. You can initialize a return connection with the following line:
NetConnect( $Handle );
So far, this chapter focused on the "standard" method of creating and registering an event handler using the NetRegister statement. For applications that require more control over connections, TSD Script provides an alternative set of statements that can be used together to register an event handler. These statements are:
NetAccept event handlers are very similar to NetRegister event handlers.
Note: You do not have to use either the NetListen or NetAccept statements to achieve a fully functional networked application.
This table summarizes the differences between NetRegister and NetListen event handlers.
Because NetListen doesn't create new instance data for each connection, the process of opening a connection through NetListen is slightly faster.
NetListen is well suited to lightweight services that do not require a context to be maintained between messages.
Comparison Item | NetRegister | NetListen |
Number of Event Handlers | There can be many NetRegister event handlers running on a server simultaneously. | There can be only one NetListen event handler defined for a server. |
Registered Service | A NetRegister event handler is registered with a specific service. The client must request that service name exactly to get a service match with the appropriate template. | A NetListen event handler does not have a specific service associated with it. A service match is made with the NetListen event handler only if there are no other matching templates on the server. |
Template | When you register a NetRegister event handler, a template of it is made. The template contains all of the connection specific information. | There is no template created when you register a NetListen event handler. The connection-specific information must be maintained by the application. |
Instance Data | Each connection has its own copy of the instance data. | There is one set of instance data defined for the event handler. Every connection opened for the event handler shares that instance data. |
Ports | The service may use an alternate port. | The service may only use the default port. |
You might have both a NetListen and a NetRegister event handler running on a server at the same time. Because a client does not know how a request is serviced, the client cannot specifically request which event handler is used.
NetListen event handlers are used for services that do not require context information to be maintained for a connection. NetListen event handlers require slightly less server resources and create connections slightly faster than NetRegister event handlers.
The client's request is matched with a NetListen event handler only if
no other match (including a wildcard template) can be found. The client sends the service
name to the NetListen event handler on the server. The NetListen
event handler must query the handle for service with NetGetService.
The client, however, has no guarantee that the service is supported by the server.
There may be multiple NetListen connections open simultaneously, since each connection shares the same instance data with other open connections. Therefore, when you close a NetListen connection, the instance data is not destroyed.
When you close a NetListen connection, you need to clean up the resources for the connection that is being closed.
The handle for each NetListen connection has the information about the specific type of service that is being requested by the client.
The NetListen event handler does not associate instance data with each connection. Instead, you must keep track of each connection and its activity. The ability to track individual connections is an added benefit of using the NetListen statement to register event handlers.
Each connection opened for NetListen shares the copy of instance data defined for the NetListen event handler. If you want to associate a specific set of instance data with one of your open NetListen connections, you can call the NetAccept statement for that connection.
Note: NetAccept can only be called for a NetListen connection. You access the connection using its handle. NetAccept can also associate a new event handler with the connection.
The instance data you associate with the connection is not used with any other connections that are currently open or that might be opened in the future for the NetListen event handler. Once you call NetAccept, the connection is altered.
All requests for the same host/service pair go directly to the new event handler.
Note: You may find it easier to use NetRegister, because the combination of NetListen and NetAccept achieves the same result.
This example illustrates how to set up a simple talk program between two networked computers. This program demonstrates the interaction between two machines in a peer-to-peer environment.
KNOWLEDGEBASE NetTalk;
CONSTANTS menuList IS { '~File', 'e~Xit', '', '~Host', '~Open', '', '~Help', '~About', '' }: LIST OF STRING; MsgUserChar IS $MsgUser; MsgRemoteChar IS $MsgUser + 1; MsgTalkClose IS $MsgUser + 2;
ROUTINES
EVENT TalkMainEvent; FUNCTION CreateTalkWindow( VAL host: NETCONNECTION ) : WINDOW; PROCEDURE NetTalkMain;
PRIVATE TYPES TALK_RECORD IS RECORD xLen: INTEGER; -- Width of window in characters yLen: INTEGER; -- Height of window in characters whdlMe: WINDOW; -- Window where local input is displayed whdlYou: WINDOW; -- Window where remote input is displayed host: NETCONNECTION; -- Handle host END;
PANNEL_DATA IS RECORD whdlParent: WINDOW; curX: INTEGER; -- X location of cursor on the window curY: INTEGER; -- Y location of cursor on the window lines: LIST OF STRING; -- List of all lines being edited END;
ROUTINES
NETCONNECTION EVENT TalkConnectEvent(REF whdl: WINDOW )IS VARIABLES result : INTEGER;
ACTIONS WHEN $Event IS $MsgNetConnect THEN -- Create a talk window. -- Create a connection to service this talk session result := NetConnect( $Handle ); IF result < 1 THEN WinMessageBox(whdl,'Error',$MBOk + $MBIconError, 'Connection failed ERROR ' & result ); Exit( 0 ); END; whdl := CreateTalkWindow( $handle ); IF whdl = $UNKNOWN THEN WinMessageBox(whdl,'Error',$MBOk + $MBIconError, 'Window creation failed' ); NetClose( $Handle ); Exit( 0 ); END; ELSWHEN MsgRemoteChar THEN -- Pass the character from the remote machine to the talk window SendMessage( whdl, MsgRemoteChar, $KeyCode ); ELSWHEN MsgTalkClose THEN NetClose( $Handle ); ELSWHEN $MsgNetClose THEN -- When the windows close on the remote machine clean up here SendMessage( whdl, $MsgClose ); NetClose( $Handle ); END; END;
EVENT TalkMainEvent IS VARIABLES host : NETCONNECTION; hostName : STRING; result : INTEGER;
ACTIONS WHEN $Event IS $MsgCreate THEN WinSetMenuBar( $Handle, menuList ); ELSWHEN $MsgMenu THEN WHEN $MenuSelection IS 101 THEN SendMessage( $Handle, $MsgClose ); ELSWHEN 201 THEN WinEditField($Desktop, hostName, 0, 0, 40, 'Enter host name', BitOr($WinTitle, $WinBorder, $WinAutoPos ) ); -- Create a talk connextion to hostname result := NetConnect( host, hostName, 'Talk' ); IF result <> 1 THEN WinMessageBox($Handle, 'Error', $MBOk + $MBIconError, 'Connection failed ERROR ' & result ); END; END; END; END; EVENT TalkPannelEvent( REF pannelData: PANNEL_DATA ) IS ACTIONS WHEN $Event IS $MsgCreate THEN pannelData.curX := 1; pannelData.curY := 1; ListInsert( pannelData.lines, '' ); ELSWHEN $MsgPaint THEN -- Clear window, and re display contents WinSetFont($Handle, 'System Monospaced', 10, 0 ); WinClear($Handle ); WinWriteLn($Handle, pannelData.lines ); ELSWHEN $MsgChar THEN -- The parent window processes all characters entered SendMessage(pannelData.whdlParent, $MsgChar, $KeyCode ); ELSWHEN MsgUserChar THEN WHEN $KeyCode IS $KeyReturn THEN -- Enter key is a new line pannelData.curX := 1; pannelData.curY := pannelData.curY + 1; ListInsert(pannelData.lines, '' ); ELSE -- Add character to current line, and display it WinSetFont($Handle, 'System Monospaced', 10, 0 ); WinGotoXY($Handle, pannelData.curX, pannelData.curY ); WinWrite($Handle, Char( $KeyCode ) ); pannelData.curX := pannelData.curX + 1; pannelData.lines[ $CURRENT ] := StrInsert(pannelData.lines [ $CURRENT ], Char( $KeyCode ), 1000 ); END; END; END;
EVENT TalkEvent( REF talkData: TALK_RECORD ) IS VARIABLES pannel : PANNEL_DATA; yLen : INTEGER;
ACTIONS WHEN $Event IS $MsgCreate THEN -- Create 2 display panels for local, and remote characters and pass in parent pannel.whdlParent := $Handle; yLen := talkData.yLen / 2; WinCreate($Handle, talkData.whdlMe, TalkPannelEvent{ pannel }, 0, 0, talkData.xLen, yLen, '', $WinField ); yLen := talkData.yLen - yLen; WinCreate($Handle, talkData.whdlYou, TalkPannelEvent{ pannel }, 0, yLen + 1, talkData.xLen, yLen,'', $WinField ); ELSWHEN $MsgDestroy THEN PostMessage( talkData.host, MsgTalkClose ); ELSWHEN $MsgSize THEN -- Position and size panels to fit window when it is resized talkData.xLen := $EventParm( 1, INTEGER ); talkData.yLen := $EventParm( 2, INTEGER ); yLen := talkData.yLen / 2; SendMessage(talkData.whdlMe,$MsgSetSize talkData.xLen, yLen); yLen := talkData.yLen - yLen; SendMessage(talkData.whdlYou,$MsgSetSize, talkData.xLen,yLen ); SendMessage(talkData.whdlYou,$MsgMove,1, yLen + 1 ); ELSWHEN $MsgChar THEN -- Send local characters to top pannel for display SendMessage(talkData.whdlMe, MsgUserChar, $KeyCode ); -- also send character to remote host to display PostMessage(talkData.host, MsgRemoteChar, $KeyCode ); ELSWHEN MsgRemoteChar THEN -- Send remote character to bottom pannel for display SendMessage(talkData.whdlYou, MsgUserChar, $KeyCode ); ELSWHEN $MsgPaint THEN WinClear( $Handle ); END; END;
FUNCTION CreateTalkWindow( VAL host : NETCONNECTION ) : WINDOW IS VARIABLES whdlNetTalk: WINDOW; talkData: TALK_RECORD; result: INTEGER;
ACTIONS talkData.host := host; result := WinCreate($Desktop, whdlNetTalk, TalkEvent{talkData}, 0, 0, 0, 0, NetGetHostName(host), BitOr($WinBorder, WinTitle, $WinResize, $WinSysMenu, $WinAutoSize, $WinAutoPos, $WinTaskList)); IF result < 1 THEN WinMessageBox($Desktop, 'Error', $MBOk + $MBIconError, 'Cannot create talk main window. Error: '&result ); END; EXIT( whdlNetTalk ); END;
PROCEDURE NetTalkMain IS VARIABLES whdlNetTalk: WINDOW; result: INTEGER;
ACTIONS result := WinCreate($Desktop, whdlNetTalk, TalkMainEvent, 0, 0, 40, 0, 'Network talk program', BitOr($WinBorder, $WinTitle, $WinResize, $WinSysMenu, $WinMenu, $WinTaskList)); IF result < 1 THEN WinMessageBox($Desktop, 'Error', $MBOk + $MBIconError, 'Cannot create talk main window. Error: ' &result ); END; NetRegister( TalkConnectEvent, 'Talk' ); WinWait( whdlNetTalk ); END;
Tivoli Service Desk 6.0 Developer's Toolkit Script Programming Guide