
The following commands are available to the client when connected to the server:
/* RCLIENT.CMD - IBM REXX Sample Program */
Parse Arg Server
/* check command line arguments, server is required */
If Server = "" Then
Do
Say "Usage: RCLIENT Servername"
Say " Servername may contain a port number separated",
"with a colon."
Exit 1
End
/* Load REXX Socket library if not already loaded */
If RxFuncQuery("SockLoadFuncs") Then
Do
Call RxFuncAdd "SockLoadFuncs","RXSOCK","SockLoadFuncs"
Call SockLoadFuncs
End
/* Connect to remote control server */
Socket = Connect(Server)
If Socket = -1 Then
Exit 1
/* loop until QUIT command was entered */
Do Until Command = "QUIT"
Say "Please enter one of: 'DIR [path]', 'TYPE name'",
" or 'QUIT'"
Parse Pull CommandLine
Parse Upper Var CommandLine Command Option
If Length(Command) > 0 Then
Call SendCommand Socket, CommandLine
End
/* Close connection to server */
Call Close Socket
Exit
The client program requires the alias name of the remote control server as
a commandline argument. In addition to the alias you can choose to use a
different well known port address on the server instead of the predefined
value of 1234. To specify another port address you have to append a colon
and the new port number to the server alias (without any inserted blanks).
To connect to server 'myserver' at port 4321 you would start the client with:
RCLIENT myserver:4321
If a valid server alias was supplied as an argument the program connects
to the server by calling the function 'Connect'. If for any reason
no socket could be created to connect to the server, this function will
return -1 and the main program will be terminated.
The main part of the client program is the loop where the user is asked to enter a command to be executed on the server until 'QUIT' is entered. The command read from the keyboard will be sent to the server with the 'SendCommand' function that will also display the response from the server. Finally, if the user has entered 'QUIT' to leave the client, the socket communication will be closed and the program terminated.
The 'Connect' function itself separates the server alias from the port number (if specified), determines the IP address of the server, creates a socket and connects it to the specified server. On completion it will return the socket handle to the caller or -1 if an error occurred:
/********************************************************/
/* */
/* Function: Connect */
/* Purpose: Create a socket and connect it to server. */
/* Arguments: Server - server name, may contain port no.*/
/* Returns: Socket number if successful, -1 otherwise */
/* */
/********************************************************/
Connect: Procedure
Parse Arg Server
/* if the servername has a port address specified */
/* then use this one, otherwise use the default port */
/* for the remote control server (1234) */
Parse Var Server Server ":" Port
If Port = "" Then
Port = 1234
/* resolve server name alias to dotted IP address */
rc = SockGetHostByName(Server, "Host.!")
If rc = 0 Then
Do
Say "Unable to resolve server:" Server
Return -1
End
/* create a TCP socket */
Socket = SockSocket("AF_INET", "SOCK_STREAM", "0")
If Socket < 0 Then
Do
Say "Unable to create socket"
Return -1
End
/* connect the new socket to the specified server */
Host.!family = "AF_INET"
Host.!port = Port
rc = SockConnect(Socket, "Host.!")
If rc < 0 Then
Do
Say "Unable to connect to server:" Server
Call Close Socket
Return -1
End
Return Socket
The 'SendCommand' function sends the specified command to the server
where it will be processed. The response from the server will be read and
acknowledged line by line. The reason for this implementation is the
following: by sending only one line from the server and then waiting for an
acknowledgement from the client we save some program logic to handle more
than one line received on the client at once. This could happen, when multiple
'SockSend' calls on the server are processed before the client reads the
results from the socket. In this case all the data would be read from the
socket at once. We could add CR/LF characters to the data sent to the client
but then we also would have to split the lines on the client to be able to
recognize the end of transmission string. By sending line by line we can
always assume that the end of transmission indicator will be received as a
standalone string. The value used for the acknowledgement from the client
to the server is not relevant, as long as it is not an empty string.
/********************************************************/
/* */
/* Procedure: SendCommand */
/* Purpose: Send a command via the specified socket */
/* and display the full response from server.*/
/* Arguments: Socket - active socket number */
/* Command - command string */
/* Returns: nothing */
/* */
/********************************************************/
SendCommand: Procedure
Parse Arg Socket, Command
/* send the command to the remote control server */
Call SockSend Socket, Command
Do Forever
BytesRcvd = SockRecv(Socket, "RcvData", 1024)
/* error or end of response encountered */
If BytesRcvd <= 0 |,
RcvData = ">>>End_of_transmission<<<" Then
Leave
/* display response and send acknowledge to server */
Say RcvData
Call SockSend Socket, "OK!"
End
Say "----- end of output from command:" Command "-----"
Return
The 'Close' function finally shuts down the used socket and closes
it. According to the socket documentation a call to 'SockClose' should be
sufficient to close down the socket connection. However experience shows
that a preceding call to 'SockShutDown' cleans up the environment better
and the sample programs can be run again immediately without experiencing
problems with ports being still in use.
/********************************************************/
/* */
/* Procedure: Close */
/* Purpose: Close the specified socket. */
/* Arguments: Socket - active socket number */
/* Returns: nothing */
/* */
/********************************************************/
Close: Procedure
Parse Arg Socket
Call SockShutDown Socket, 2
Call SockClose Socket
Return
/* RSERVERD.CMD - IBM REXX Sample Program */
Parse Arg Port
If Port = "" Then
Port = 1234
/* Load REXX Socket library if not already loaded */
If RxFuncQuery("SockLoadFuncs") Then
Do
Call RxFuncAdd "SockLoadFuncs","RXSOCK","SockLoadFuncs"
Call SockLoadFuncs
End
/* Open socket at well known port and wait for clients */
Socket = ListenPort(Port)
If Socket = -1 Then
Exit 1
/* close the socket when program is interrupted */
Signal On Halt
Do Forever
/* wait for client to connect and start handler */
Say "Waiting for client to connect."
Say "Press Ctrl-C to exit program."
ClientSocket = SockAccept(Socket)
Say "Client connected, starting handler process."
"start rserverh.cmd" ClientSocket
End
Halt:
Call Close Socket
Exit
The logic of the remote control daemon is quite simple. It creates a
socket, connects it to a well known port address (which can be either
the default value for our application or a user defined port address)
and then enters an endless loop waiting for clients to connect. As soon
as a client has connected it starts a new session with the handler
program which then takes over the communication with the client. The
loop waiting for clients is an infinite loop which can only be
interrupted by stopping the program with Ctrl-C. A signal handler for
the 'HALT' signal has been added to cover this event to correctly close
down the socket.
The 'ListenPort' function of the daemon program creates a new socket at a well known port address waiting for clients to connect:
/********************************************************/
/* */
/* Function: ListenPort */
/* Purpose: Create a socket, bind it to a port and */
/* listen at the port for connecting clients.*/
/* Arguments: Port - port number */
/* Returns: Socket number if successful, -1 otherwise */
/* */
/********************************************************/
ListenPort: Procedure
Parse Arg Port
/* create a TCP socket */
Socket = SockSocket("AF_INET", "SOCK_STREAM", "0")
If Socket < 0 Then
Do
Say "Unable to create socket"
Return -1
End
/* find out local IP address and bind socket to port */
Host.!addr = SockGetHostId()
Host.!family = "AF_INET"
Host.!port = Port
rc = SockBind(Socket, "Host.!")
If rc < 0 Then
Do
Say "Unable to bind to port:" Port
Call Close Socket
Return -1
End
/* listen at the port, allow 5 clients in queue */
rc = SockListen(Socket, 5)
If rc < 0 Then
Do
Say "Unable to listen at port:" Port
Call Close Socket
Return -1
End
Return Socket
The 'ListenPort' creates a connection request queue for up to 5
concurrent connection requests from clients. If any of the socket calls
fails the function will return a value of -1 to indicate a failure. If
no problem occurred the socket handle will be returned.
The last function in the daemon program is the 'Close'. Since it is identical to the 'Close' function of the client program it is not listed again.
As already mentioned in the client program the protocol used for the communication between client and handler expects that every line sent from the handler is acknowledged by the client. The end of the result of a processed command will be marked with a special 'End of transmission' indicator.
The handler process uses two functions to send lines back to the client. The function 'Answer' sends a single line to the client and waits for an acknowledgement while the function 'AnswerQueue' is used to send all lines available in the REXX session queue to the client. The latter function makes it extremely easy to implement the two commands provided by the server. Both commands can be executed on the server workstation without modifications by the OS/2 command processor. The output of the commands will be captured and redirected into the REXX session queue by piping the output into RXQUEUE.EXE. Unnamed session queues are used in this example because such a queue is needed for every handler process running on the server. Named queues are shared among all processes on a PC and are therefore not suited for our purposes.
The server commands itself are processed in the functions 'ProcessDirCommand' and 'ProcessTypeCommand' respectively. This is the complete listing of the handler program:
/* RSERVERH.CMD - IBM REXX Sample Program */
Parse Arg Socket
/* Load REXX Socket library if not already loaded */
If RxFuncQuery("SockLoadFuncs") Then
Do
Call RxFuncAdd "SockLoadFuncs","RXSOCK","SockLoadFuncs"
Call SockLoadFuncs
End
/* Show some information about connected client */
Call SockGetPeerName Socket, "ClientAddr.!"
Call SockGetHostByAddr ClientAddr.!addr, "ClientAddr.!"
Say "Established connection with client '" ||,
ClientAddr.!name || "'."
/* Process commands until QUIT is reached */
Command = ""
Do Until Command = "QUIT"
Command = ReceiveRequest(Socket)
End
/* Close socket and OS/2 session */
Call Close Socket
"Exit"
/********************************************************/
/* */
/* Function: ReceiveRequest */
/* Purpose: Wait for a command from the client and */
/* execute it. Return the identifier of the */
/* command to the caller. */
/* Arguments: Socket - active socket number */
/* Returns: command identifier */
/* */
/********************************************************/
ReceiveRequest: Procedure
Parse Arg Socket
/* Wait for the command from the client */
BytesRcvd = SockRecv(Socket, "CommandLine", 1024)
Say "Command line from client:" CommandLine
Parse Var CommandLine Command Option
Command = Translate(Command)
Select
When Command = "DIR" Then
Call ProcessDirCommand Socket, Option
When Command = "TYPE" Then
Call ProcessTypeCommand Socket, Option
When Command = "QUIT" Then
Nop
Otherwise
Call Answer Socket, "Invalid command."
End
/* send end of answer marker back to client */
Call SockSend Socket, ">>>End_of_transmission<<<"
Return Command
/********************************************************/
/* */
/* Procedure: Close */
/* Purpose: Close the specified socket. */
/* Arguments: Socket - active socket number */
/* Returns: nothing */
/* */
/********************************************************/
Close: Procedure
Parse Arg Socket
Call SockShutDown Socket, 2
Call SockClose Socket
Return
/********************************************************/
/* */
/* Procedure: Answer */
/* Purpose: Send one answer line back to the client */
/* and wait for acknowledgement from client. */
/* Arguments: Socket - active socket number */
/* AnswerString - line to send to client */
/* Returns: nothing */
/* */
/********************************************************/
Answer: Procedure
Parse Arg Socket, AnswerString
Call SockSend Socket, AnswerString
Call SockRecv Socket, "Ack", 256
Return
/********************************************************/
/* */
/* Procedure: AnswerQueue */
/* Purpose: Send all lines from the session queue */
/* back to the client as the answer of the */
/* previous executed command. */
/* Arguments: Socket - active socket number */
/* Returns: nothing */
/* */
/********************************************************/
AnswerQueue: Procedure
Parse Arg Socket
/* send answer lines until session queue is empty */
Do While Queued() > 0
Parse Pull Line
/* empty lines will be sent as a space */
If Line = "" Then
Line = " "
Call Answer Socket, Line
End
Return
/********************************************************/
/* */
/* Procedure: ProcessDirCommand */
/* Purpose: Process the DIR command that was received */
/* from the client and send back the result. */
/* Arguments: Socket - active socket number */
/* FileMask - optional file mask for DIR */
/* Returns: nothing */
/* */
/********************************************************/
ProcessDirCommand: Procedure
Parse Arg Socket, FileMask
/* redirect output from DIR command to session queue */
"DIR" FileMask " | RXQUEUE"
Call AnswerQueue Socket
Return
/********************************************************/
/* */
/* Procedure: ProcessTypeCommand */
/* Purpose: Process the TYPE command that was received*/
/* from the client and send back the result. */
/* Arguments: Socket - active socket number */
/* FileName - required filename to type */
/* Returns: nothing */
/* */
/********************************************************/
ProcessTypeCommand: Procedure
Parse Arg Socket, FileName
/* TYPE needs a filename as argument */
If FileName = "" Then
Do
Call Answer Socket, "You have to specify a filename",
" with the TYPE command."
Return
End
/* Check if the file really exists */
If Stream(FileName, "C", "QUERY EXISTS") = "" Then
Do
Call Answer Socket, "The specified file '" ||,
FileName || "' does not exist."
Return
End
/* redirect output from TYPE command to session queue */
"TYPE" FileName " | RXQUEUE"
Call AnswerQueue Socket
Return