previous next top contents index framed top this page unframed
The communicating processes must be aware that they are communicating with another process and they must agree on a protocol for exchange of information, including who speaks first, how the conversation is terminated, what kind of buffering is used, the required size of the buffers, what kind of objects are written (e.g., characters or storage units), and what those objects mean. This communications protocol is distinct from and “built on top of” the underlying network protocol used to carry the bytes between the processes.
Creation of sockets for client-server communication is described in Section 7.3. Creation of child processes is described in Section 7.4.
A high-level facility for remote MODULE creation that does not require the user to deal explicitly with interprocess communication is described in Chapter 5.
7.1. Meaning of $eos on Socket Streams
On both input and output, $eos
means that process at the other end has
broken (hung up) the connection.
7.2. Socket-Specific Fields of $stream
The socket-specific fields of $stream are shown in
Table 7–1.
Table 7–1. Socket-Specific Fields of the Stream Record
| STRING $myHostName, # Our host's name $hisHostName, # Other end's host's name $serverName, # The server we are talking to, or # "" if we are not talking to a # server. $protocolName; # The network protocol name, or "" # if the socket is a simulated # internal socket. |
It should rarely be necessary for the typical programmer to write a low-level server; the RPC facilities described in Chapter 5 should be sufficiently general for most applications and are much simpler to use than the facilities described in this chapter.
7.3.1. Terminology and Conventions
A “service” is a set of functions available under a global
(system- or network-wide) name;
like a file name, the service name can be used by any process that
wants to make use of the service.
The service functions are available through a “high-level
protocol” (defined by the author of the service) that specifies
the semantics of data transferred to and from the service.
The data are transmitted according to a “low-level protocol”
(or “network protocol” or just “protocol”)
specified by the operating system; a given service may be available
only through a single low-level protocol or through several different
low-level protocols.
A “server” is a process that provides one or more services. When the process wishes to start handling the functions of a given service, it must make calls to the operating system to inform it that is ready to do so. In MAINSAIL, this is done with the $bindService and $acceptClient calls.
A “port” is a handle (used on the server end) that represents an active service. It is represented by a POINTER to a record of the MAINSAIL CLASS $port. In a process that wishes to become a server for a particular service, the normal sequence is to verify that it is legal to become a server for that service by calling $bindService, and then, if $bindService returned a non-Zero port, to pass the port to $acceptClient. $acceptClient is typically called in a loop; each time it produces a stream, a new coroutine is created to talk through the stream.
A “client” is a process that makes use of a service. It communicates with the service through a stream. It can use the stream prefix SERVICE to request a connection to the service on any available low-level protocol, or it can use a stream MODULE named for a low-level protocol to specify the low-level protocol (this may be useful where a service supports more than one low-level protocol).
A “connection” is
the two-way channel between a client and a service.
It is represented as a stream on each end;
the server stream is obtained from the call to $acceptClient.
If a server expects to receive more than one connection at a time,
it must set up a scheduled coroutine for each possible connection. Example 7–2. Client Access to a Service
Clients may open a stream to a service using the stream prefix
SERVICE.
At a minimum, the service name must be provided. The host
name may also be given. If the host name is omitted, $openStream
automatically determines the host on which the service is available.
SERVICE automatically maps to an appropriate network
protocol MODULE,
e.g., TCP.
The available network protocol MODULEs are listed in
Appendix B.
Examples:
The network protocol MODULE to use
to talk to the service may be given instead of using the generic
SERVICE stream MODULE,
although it is rarely necessary to be so
specific:
instead of:
This obsolescent syntax is still supported for UNIX, but is not
portable, and its use in new code is discouraged.
7.3.2. Services Table
A global table of registered services, the services
table, is maintained on
each node. All services
must be registered in this table before they can be used.
The possible table formats are described in Section A.1.
7.3.3. The Client End
$openStream(st,"service>serviceName");
or:
$openStream(st,"service(host)>serviceName");
NOTE: Up until MAINSAIL Version 15.14,
the implementation required the following syntax
to specify a host name:
service>host:serviceName
tcp>host:serviceName
service(host)>serviceName
tcp(host)>serviceName
7.3.4. The Server End
STRING
PROCEDURE $myHostName;
STRING
PROCEDURE $canonicalHostName
(STRING hostName);
STRING
PROCEDURE $canonicalUserID
(STRING userID,
hostName);
INTEGER
PROCEDURE $getHosts (STRING serviceName;
MODIFIES STRING ARRAY(1 TO *)
hosts;
PRODUCES OPTIONAL STRING eMsg);
INTEGER
PROCEDURE $getProtocols
(STRING hostAndServiceName;
MODIFIES STRING ARRAY(1 TO *)
prots;
PRODUCES OPTIONAL STRING eMsg);
POINTER($port)
PROCEDURE $bindService
(STRING protocolAndServiceName;
OPTIONAL LONG INTEGER timeOut;
OPTIONAL BITS ctrlBits;
PRODUCES OPTIONAL STRING eMsg);
LONG INTEGER
PROCEDURE $unbindService
(MODIFIES POINTER($port) p;
OPTIONAL LONG INTEGER timeout;
OPTIONAL BITS ctrlBits);
LONG INTEGER
PROCEDURE $acceptClient
(POINTER($port) p;
PRODUCES POINTER($stream) st;
OPTIONAL LONG INTEGER timeout;
OPTIONAL BITS ctrlBits);
BOOLEAN
PROCEDURE $parseHostServiceName
(STRING name;
PRODUCES STRING host,service;
PRODUCES OPTIONAL STRING eMsg);
A server typically uses $getProtocols to discover the network protocols it is expected to support for each service. It then starts a separate coroutine to handle each protocol/service pair. For each such pair, the server uses $bindService to bind itself to a port for the protocol and service and repeatedly calls $acceptClient to accept new clients on that port. Whenever $acceptClient returns a new stream, a new coroutine is created to talk through that stream and perform the service functions on the connection represented by the stream.
$myHostName returns the host name of the current host (this is the canonical name as specified in the services table). If the host name is not available, $myHostName returns the empty STRING. The $myHostName field of a socket stream st gives the host name of the system on which the program is running, i.e.:
st.$myHostName = $myHostName
$canonicalHostName returns the official name of the host named by hostName according to the MAINSAIL services table. It returns the original STRING if hostName is not in the table.
$canonicalUserID takes a STRING userID (as returned from the $userID system PROCEDURE) and a STRING hostName (as returned from the $myHostName STREAMS system PROCEDURE) and returns a user ID STRING unique at the current site, provided an appropriate entry has been set up in the XIDAK services table.
The user ID is mapped by an entry in the XIDAK services table. Currently, only the mslhosts format of the services table allows such an entry; you cannot use $canonicalUserId if you are using the xidakservices format of the table (see Section A.1). The mslhosts entry format is:
USERID canonicalName userID1/hostName1 ... userIDn/hostNamen
$canonicalUserID returns canonicalName if userID equals userIDi and hostName equals hostNamei. If there is no entry for the userID/hostName pair in the services table, $canonicalUserID returns userID.
$getHosts returns the number of host names that support the service serviceName; the actual host names are in the ARRAY hosts (so that the return value from a successful call to $getHosts is hosts.ub1). Using $getHosts, a server may determine if any other copies of itself might be running on other systems. $getHosts is also used internally when a client opens a stream with a name of the form service>xxx (where xxx is a service). In this case, if the service xxx is uniquely available on a single host, the host name is filled in automatically. $getHosts returns 0 with eMsg set to an appropriate error message if an error occurs.
$getProtocols returns the network protocols through which hostAndServiceName may be accessed from the current host. The call is provided for servers so they can set themselves up to service the appropriate set of network protocols.
$getProtocols modifies the input ARRAY (allocating it if NULLARRAY is passed) to contain all of the network protocols required to support the specified service on the specified host, and it returns the number of such protocols in the ARRAY. The information returned by $getProtocols is currently derived from the services table. If no protocols are available, $getProtocols returns 0, with eMsg set to an appropriate error message. hostAndServiceName may take one of the forms shown in Table 7–4.
Table 7–4. Valid hostAndServiceName Values ($getProtocols)
| hostAndServiceName | Returned Network Protocols |
|---|---|
| "" | All that the current host speaks |
| "serviceName" | All under which "serviceName" is available on the current host |
| "(host)>serviceName" | All under which "serviceName" is available on the host with the given name |
| Note 1: The host name may be a valid MAINSAIL identifier, or it may be an internet address (e.g., "192.9.200.201"). | |
| Note 2: The service name is assumed to be a valid MAINSAIL identifier. | |
$bindService accepts a single network protocol and service name and returns a POINTER to a port. A server typically binds itself to one port for each network protocol that the host system supports.
The protocolAndServiceName argument has the format:
protocol>serviceName
The protocol> part is optional. If the protocol is omitted, the protocol defaults to the first one listed in the services table for the named service on the current host. The available protocol MODULEs are listed in Appendix B.
Figure 7–5 shows the user-visible fields of a $port record. The service name and host name are those resolved by the $bindService call. $lastError is set by $acceptClient if an error return is made.
Figure 7–5. Fields of $port
CLASS $port (
STRING
$serviceName, # As supplied to/resolved by $bindService
$hostName; # " " " " " "
STRING
$lastError; # Set by $acceptClient
);
If the service cannot be bound, $bindService returns NULLPOINTER with eMsg set to an appropriate error message. If timeout is a positive number, the call returns when that many milliseconds have elapsed. Specify a timeout value of $block to wait indefinitely.
The $unbindService PROCEDURE is used if the server decides to stop accepting clients on a port.
The possible return values from $unbindService are described in Section 10.1.2.
The $acceptClient PROCEDURE accepts a port (returned by $bindService) and produces a stream. It blocks until a client requests a connection to the server on the port p (the client uses $openStream to open its end). The server then talks to the client through the stream produced by $acceptClient. At the end of the interaction, the server and the client each call $closeStream on their respective streams to terminate the connection. $acceptClient returns standard stream error codes; i.e., $success($bindService(...)) returns TRUE if the service can be bound, otherwise FALSE, with p.$lastError set to an error message STRING. On a FALSE return, the value is either $error or $timedOut, if a positive timeout value (milliseconds) was given. Specify $block for timeout if the PROCEDURE should wait indefinitely.
A call to $acceptClient can raise the exception $tooManyOpenFilesStr if it is impossible to accept the client because no more stream handles are available. If the exception is handled by falling out of the handler, the call is aborted. If the exception is propagated, an error message is issued. If a handler of this exception does a $raiseReturn, it is assumed that the handler closed some files, so the $acceptClient is attempted again.
$parseHostServiceName parses a name into the STRINGs host and service, handling quoted names and the fact that the host name may be omitted. This PROCEDURE is rarely needed by user code. It returns TRUE if successful, FALSE with eMsg set to an explanation if it cannot parse the name.
7.3.5. STREAMS PROCEDUREs Supported by xidakservices
xidakservices supports three PROCEDUREs
that provide information
about available services:
Figure 7–6. Server PROCEDUREs Supported Only by xidakservices
INTEGER
PROCEDURE $getRemoteModules
(STRING hostAndServiceName;
MODIFIES STRING ARRAY(1 TO *)
mods;
PRODUCES OPTIONAL STRING eMsg);
STRING
PROCEDURE $getServerProperty
(STRING hostAndServiceName;
STRING propertyName;
PRODUCES OPTIONAL STRING eMsg);
INTEGER
PROCEDURE $getServersForRemoteModule
(STRING remoteModuleName;
MODIFIES STRING ARRAY(1 TO *)
servers;
PRODUCES OPTIONAL STRING eMsg);
These PROCEDUREs are similar to the routines $getHosts and $getProtocols in that they return information from the services table. They are not implemented for any lookup method except xidakservices (see Section A.1).
$getRemoteModules modifies mods to contain the names of the remote MODULEs supported by the server hostAndServiceName (according to the entry in xidakservices). It returns:
| n | where n is the number of remote MODULEs supported by hostAndServiceName if hostAndServiceName is found. |
| 0 with eMsg NEQ "" | if the service is not found or the service is ambiguous. |
| 0 with eMsg = "" | if the service has no remote MODULEs. |
$getServerProperty gets the value of the property propertyName for the server hostAndServiceName. It returns:
| the value | if the value is found. |
| "" with eMsg NEQ "" | if the server name is not found or the server name is ambiguous. |
| "" with eMsg = "" | if the server does not have the property or the property's value is the empty STRING. |
Both $getRemoteModules and $getServerProperty accept hostAndServiceName in either the form serviceName or the form hostName:serviceName. Omitting the host works only if the service is unique to one host; i.e., the same rules are applied as when you call:
$openStream(st,"service>" & hostAndServiceName,...);
or:
$newRemoteModule(hostAndServiceName,...);
$getServersForRemoteModule returns an ARRAY of multiclient servers in the xidakservices file that support a given remote MODULE. This PROCEDURE is intended to allow an application program to display a list of possible servers to a user.
SERVICE entries in the xidakservices table with an unspecified host name are returned as a single ARRAY element containing only the service name, rather than attempting to obtain the name of every host in the network and creating multiple ARRAY elements for them. Accessing such a server without prepending the host name gives an error message because service names without a host are ambiguous.
Currently, the only use MAINSAIL STREAMS and RPC make of the SUPPORTS clause in the SERVICE entry is to implement this PROCEDURE for the benefit of MAINSAIL applications.
Currently, C applications must parse $SERVICEDIR/xidakservices themselves if they want the information provided by these three MAINSAIL PROCEDUREs.
7.3.6. The Services Table
The server/client implementation currently depends on the
availability of a file describing the services available on each host.
The logical name (xidak services) or
(service protocol table) (depending on the table format used)
must be defined using
a MAINEX ENTER subcommand
in the MAINSAIL startup command file to specify the file
containing the table.
The services table may eventually be replaced by a XIDAK name
server. See Appendix A.
7.4. Child Process Creation: PROCESS and PTYPRO
In the parent/child process control model, a parent process creates one
or more child processes to do its bidding. The children may in turn
become parents of additional processes.
When the parent process exits, the children processes (and all of their children) are usually terminated if they are still active.
The parent/child process control model is similar to the scheduled coroutine model. In fact, it is possible to write programs that communicate through a stream with some other entity that is either a separate process or another coroutine in the current process.
7.4.1. Starting a Child Process Using PROCESS
Child processes are started using
the $openStream PROCEDURE and the
stream MODULE PROCESS.
The name of the stream has the form:
process{(hostName)}>programName {programArgs}<eol>
{userName}<eol>
{password}<eol>
{startupDirectory}<eol>
{numberOfEnvironmentVariables}<eol>
{environmentVariable1}<eol>
.
.
.
{environmentVariablen}
(where the curly brackets indicate optional elements; they are not part of the syntax).
On UNIX, an obsolescent alternate syntax allows the first line to be replaced with:
process>{hostName:}programName {programArgs}<eol>
socpro>{hostName:}programName {programArgs}<eol>
but the UNIX syntax is not portable to other operating systems; its use in new code is discouraged.
The bracketed items are optional. Trailing blank lines can be omitted.
A new process is started on the current host by running the program stored in the file named by programName. If a host name is specified, the new process is started on that host instead of the current system. The gensrv server process must be running on the specified host if a host name is given.
PROCESS examines the ENTER and SEARCHPATH subcommands specified in the parent process and maps programName accordingly. On UNIX, it also uses the parent process's PATH environment variable.
If programArgs is given, those arguments are made available to the program, provided that program arguments are supported by the operating system.
The process is started with the same user ID as the current process, unless userName is given, in which case it is used to set the user ID for the process. password is required if the current process would not normally have the right to run as the specified userName.
Note: you must be superuser in order to specify non-null userName and password parameters.
If startupDirectory is specified, it becomes the current directory for the new process; otherwise, the new process's current directory is the same as the parent process's (if on the same machine) or the home directory for the user ID (if on a remote machine).
Environment variables can be passed to the child process. numberOfEnvironmentVariables is the number (in textual form) of environment variable lines that follow. Each environment variable line has the form:
VARIABLENAME=value
For example, to create the process named thisProgram, with arguments hello and goodbye, running under the user ID george, connected to /user/dir, with the environment variables ENV1 set to /usr/baz and ENV2 set to hostname, you would open the following stream:
$openStream(st,
"PROCESS>thisProgram hello goodbye" & eol &
"george" & eol &
"george's password" & eol &
"/user/dir" & eol &
"2" & eol &
"ENV1=/usr/baz" & eol &
"ENV2=hostname");
In addition, programName itself can be an environment variable of the current program if it begins with a dollar sign on UNIX, or if it is surrounded by percent signs on Windows (other systems may follow other conventions for environment variables). For example, if the environment variable environVar is defined as foo, then on UNIX:
$openStream(st,"PROCESS>$environVar");
or on Windows:
$openStream(st,"PROCESS>%environVar%");
is equivalent to:
$openStream(st,"PROCESS>foo");
The $openStream call returns a socket stream that talks directly to the child's $tty stream. Thus, for the child, $isaTty($tty) returns FALSE. Input for the child may be supplied by calling $writeStream, and output from the child may be read using $readStream.
Characters written to the process stream are not echoed back. This
form of process control is useful for direct interprocess communication.
It is very similar to the use of pipes on UNIX.
7.4.2. Starting a Child Process Using PTYPRO
A child process may also be started using
the $openStream PROCEDURE and
the stream MODULE PTYPRO.
The name of the stream is similar to that
for PROCESS; it has the form:
ptypro>programName {programArgs}<eol>
{userName}<eol>
{password}<eol>
{startupDirectory}<eol>
{numberOfEnvironmentVariables}<eol>
{environmentVariable1}<eol>
.
.
.
{environmentVariablen}
A new process is started on the current host by running the program stored in the file named programName. If a host name is given, the gensrv server process must be running on it. If neither programName nor programArgs is given, and the system supports running a “shell” as a child process, the standard system shell is run. The remaining arguments are optional and are interpreted as for PROCESS.
The $openStream call returns a PTY stream to talk to the child's $tty. Thus, for the child, $isaTty($tty) is TRUE. Talking to the PTY is much like sitting at a keyboard in front of a terminal screen. Characters written to the stream are echoed back, if the running program wants them to be.
This kind of connection has a specialized but useful application for system shells and windowing systems. The program that creates the process (e.g., the shell or window manager) serves merely as the intermediary between the actual user and the created process.
Special considerations of the PTY stream type are discussed in Section 11.9.
Many systems do not provide general pseudoterminal support. On such
systems, PTYPRO is not available. Figure 7–7. $executableBootName
7.4.3. Starting a MAINSAIL Child Process
STRING
PROCEDURE $executableBootName
(OPTIONAL STRING hostName);
$executableBootName returns the name of the standard MAINSAIL executable bootstrap on hostName (on the local system if hostName is omitted). It returns the empty STRING if the information is not available.
A MAINSAIL child process can be started on the current system with a stream name of the form:
"process>" & $executableBootName
If the operating system supports command line arguments, a particular MAINSAIL MODULE can be started in a child process using a stream name of the form:
"process>" & $executableBootName & " " & modName
where modName is the desired MAINSAIL MODULE.
| Implementation Note $executableBootName is currently implemented using a logical file name. $executableBootName calls lookUpLogicalName with the argument:
"(executable boot " & hostName &")" For this call to succeed on systems other than the current host, the installer of MAINSAIL must define the standard bootstrap names of all host systems in MAINEX ENTER subcommands (typically in the local site.cmd file). The implementation may be changed in the future to use a more flexible mechanism. |
Typically, the cooperative communication with the child takes place over a control stream that is separate from the child's $tty. This control stream is available to the child as the STREAMS system variable $parent and may be established by the parent using the two-POINTER form of $openStream, e.g.:
$openStream(childTty,childControl,"process>.....");
Typically, childTty is used by the child only for error messages
and when the debugger is invoked in the child.
The childControl stream
is typically used for all cooperative communication between parent and
child.
7.5. Terminating a Child Process
A child process created with $openStream
may be terminated by closing
the stream with $closeStream.
If the process terminates by itself before
the $closeStream call,
the currently active or next I/O call to the
process's stream returns with an error.
7.6. Process Control
If the started process is a cooperating child program designed to run as
a child process, the parent and child can use a communications protocol
of their choice.
Any stream I/O mechanism may be used, including $packet
I/O.
If the started process is a noncooperating child program connected to a PTY stream, a special character may be sent to the process to cause it to interrupt. This character is provided as a field of the CLASS $stream (see Chapter 11). Alternatively, the PROCEDURE $writeStreamInterrupt may be used to interrupt a noncooperating child process. If the child process has enabled interrupt handling, it may do something special based on the interrupt. Otherwise it will probably die and further reads and writes to its stream will return an error. This latter behavior can be the only effective way to kill a child of a child process on systems (e.g., if you run a process through rlogin on UNIX, the rlogin process can be terminated using $closeStream, but not the remote process started by rlogin).
No other process control is currently provided.
7.7. Examples
7.7.1. Sprouting a Print Job and Waiting for It to Exit
The simplest interaction with a child process is running a
noninteractive program and waiting for it to complete. This form of
child process control does not require scheduling.
Example 7–8 is an example of invoking a print program directly
from a MAINSAIL program.
Example 7–8. Sprouting a Print Job and Waiting
| PROCEDURE printFile (STRING name); BEGIN STRING s; POINTER($stream) child; $openStream(child,"process>print " & name); WHILE $success($readStream(child,s,errorOK)) DO write(logFile,s); $closeStream(child); END; |
printFile uses a program called print to print a file given its name. Any terminal output generated by the print program is written to its parent process's logFile. When the print program terminates, $readStream returns an end-of-stream and the child stream is closed.
This simple example does not require scheduling of advanced STREAMS for
the system on which it is run. Note, however, that the example makes
no provision for input to the child. If the child program attempts
to read from its terminal, it blocks until some input is supplied.
The parent is also blocked in $readStream waiting for the child to
output or terminate, so both processes may block indefinitely.
7.7.2. Sprouting an Interactive Child
The more general case of handling an interactive child process, that is,
a process that performs terminal input, is shown in Example 7–9.
The child is started much the way a program is started by a
system shell. All of the child's output goes to the parent's terminal,
and input from the parent's terminal goes to the child.
This form of child process control requires scheduling.
runCmd runs a child program. The child's input and output are connected to the stream returned by $openStream, so the parent must handle both using two independent coroutines. reader is started as a coroutine to read what the child writes and output it to the parent's $tty. writer is started as a coroutine to read from the parent's $tty and write the keystrokes to the child process.
The parent might also perform processing of the I/O to the child, such as logging the child's output or supplying its input from a file. Also, typically the parent will want to enable and handle keyboard interrupts and pass them on to the child. Neither of these possibilities is shown.
If either the reader or writer detects the end of the child process, both the reader and writer are killed and runCmd returns to its caller in the parent.
Example 7–9. Sprouting an Interactive Child
| POINTER($stream) pty; POINTER($coroutine) rc, wc; DEFINE print(x) = [$writeStream($tty,x,$line)]; PROCEDURE reader; BEGIN STRING s; WHILE $success($readStream(pty,s,errorOK)) DO print(s); IF wc AND NOT $killedCoroutine(wc) THEN $killCoroutine(wc); $reschedule(delete); END; PROCEDURE writer; BEGIN STRING s; DO $readStream($tty,s) UNTIL NOT $success($writeStream(pty,s,errorOK)); IF rc AND NOT $killedCoroutine(rc) THEN $killCoroutine(rc); $reschedule(delete); END; PROCEDURE runCmd (STRING args); BEGIN STRING s, t; OWN INTEGER nRun; IF NOT $openStream(pty,"ptypro>"&args,errorOK,s) THENB printError(s); RETURN END; $HANDLEB startup; nRun.+1; s := "RunReader" & cvs(nRun); t := "RunWriter" & cvs(nRun.+1); rc := $createCoroutine(thisDataSection,"reader",s); $queueCoroutine(rc); wc := $createCoroutine(thisDataSection,"writer",t); $queueCoroutine(wc); $waitForDescendants; $closeStream(pty) END $WITHB IF $exceptionName = $abortProcedureExcpt THENB IF pty THEN $closeStream(pty); cleanup END; $raise END; END; |
MAINSAIL STREAMS User's Guide, Chapter 7