Program Examples using Interprocess Communication

ORDER Client/Server

The SALES system has a program to take care of orders being placed for products. The order entry clerk must check the customer's credit rating from data held on the FINANCE system. The orders program on the SALES system is written as a client, which makes use of a server program running on the FINANCE system.

Client Program

The SALES order processing program includes the following code:

*(near the beginning of the program)
CONNECT "FINANCE":AM:"SALES-ACCOUNT":AM:...
"ORDER-SERVER" TO SESS ELSE..
* This starts and establishes contact with the
* server on the FINANCE system.
*
*(while processing an order)
INFO<1>=ORDER.NO
INFO<2>=CUSTOMER
INFO<3>=COST
*
SEND INFO TO SESS ELSE..
*
RECWAIT RESPONSE FROM SESS ELSE..
IF RESPONSE="OK" THEN
PRINT "Credit OK, order accepted by FINANCE."
END ELSE
PRINT "Credit NOT OK, do not trust this one!"
* Take care of bad customer
END
*
*(near end of program)
DISCONNECT SESS ELSE..
*

Server Program

On the FINANCE system, the program ORDER-SERVER on the SALES-ACCOUNT includes the following code:

* near the beginning
ACCEPT "ORDER-SERVER" TO SESS ELSE..
* This connects with the client process.
*
RECWAIT INFO FROM SESS ELSE..
*
ORDER.NO=INFO<1>
CUSTOMER=INFO<2>
COST=INFO<3>
*
READ CREDIT FROM CUST.FILE,CUSTOMER ELSE..
IF CREDIT>=COST THEN
    SEND "OK" TO SESS ELSE..
    WRITE INFO TO ACCOUNT.REC.FILE,ORDER.NO
END ELSE
    SEND "NOT OK" TO SESS ELSE..
END
*

Analysis

When the ORDER program runs, the SALES system talks to the FINANCE system. The operating system on FINANCE checks to see if a program has issued an ACCEPT with the server name "ORDER-SERVER". If there is an ACCEPT outstanding, a session is established between the two programs. If not, "ORDER-SERVER" is issued as a TCL command on the "SALES-ACCOUNT". This invokes the DataBasic program titled "ORDER-SERVER". When the "ORDER-SERVER" program issues its ACCEPT statement, the connection is established.

Multiple Server Program

The server program could be designed to accommodate several client programs, all programmed to make the same inquiry and obtain the same type of information from the server program.

In the following example, three client programs connect to the server program. The sessions are called SESS1, SESS2, and SESS3. The server in the CONNECT statements of the three client programs are ORD-SERV1, ORD-SERV2, and ORD-SERV3. The MD of the SALES-ACCOUNT contains items labeled ORD-SERV1, ORD-SERV2, and ORD-SERV3. Each contains the following attributes:

PQN
HRUN SALES ORDER-SERVER
P

The ORDER-SERVER program includes the following code:

ACPT1:
ACCEPT "ORD-SERV1" TO SESS1 THEN GO READ1 ELSE FLG1=0 GO READ2
ACPT2:
ACCEPT "ORD-SERV2" TO SESS2 THEN GO READ2 ELSE FLG2=0 GO READ3
ACPT3:
ACCEPT "ORD-SERV3" TO SESS3 THEN GO READ3 ELSE FLG3=0 GO READ1
*
READ1:
    IF FLG1=0 THEN GO ACPT1
    RECEIVE INFO FROM SESS1 THEN GOSUB ...
    RUN.IT ELSE GO READ2
    SEND REPLY TO SESS1 SETTING E1 ELSE ...
    GO ERR.VAL
READ2:
    IF FLG2=0 THEN GO ACPT2
    RECEIVE INFO FROM SESS2 THEN GOSUB ...
    RUN.IT ELSE GO READ3
    SEND REPLY TO SESS2 SETTING E2 ELSE ...
    GO ERR.VAL
READ3:
    IF FLG3=0 THEN GO ACPT3
    RECEIVE INFO FROM SESS3 THEN GOSUB ...
    RUN.IT ELSE GO READ1
    SEND REPLY TO SESS3 SETTING E3 ELSE ...
    GO ERR.VAL
    GOTO READ1
RUN.IT:
    ORDER.NO=INFO<1>
    CUSTOMER=INFO<2>
    COST=INFO<3>
    *
    READ CREDIT FROM CUST.FILE,CUSTOMER ELSE..
    IF CREDIT>=COST THEN
      REPLY="OK"
      WRITE INFO TO ACCOUNT.REC.FILE,ORDER.NO
    END ELSE
      REPLY="NOT OK"
    END
    RETURN
    *
ERR.VAL:
    * Evaluate error code, why can't send data

Notice that the RECWAIT statement of the original program is replaced with RECEIVE statements. If data is there to be read, it is read; otherwise, the program advances to the next receive operation.

FPUT Client/FGET Server

The example program FPUT listed below communicates with the example program FGET that follows. These programs are executed from separate processes (usually running on different systems connected to a common network, although for testing purposes they can be run on the same system).

Client Program

FPUT sets up the connection and starts FGET, then waits for input from FGET. It cycles round until the session is terminated by FGET. It then reports statistics and stops. See below.

EQUATE AM TO CHAR(254)
PRINT "REMOTE SYSTEM ":
INPUT RSYS
CRT "CONNECTING..."
CONNECT RSYS:AM:'CHRIS':AM:'FGET' TO S TIMEOUT 2 SETTING ECODE THEN
     STTIME = SYSTEM(12)
     BYTES = 0
     CNT   = 0
     XCNT  = 0
     FAILED= 0
     CRT "CONNECTED"
     LOOP UNTIL FAILED DO
        RECWAIT DT FROM S SETTING ECODE ELSE
          PRINT "RECEIVE ERROR: ":ECODE
          CRT "AVERAGE TRANSMISSION RATE = ":...
          200100/(SYSTEM(12)-SSTIME):"BYTES PER SECOND."
          DISCONNECT S SETTING ECODE ELSE
            PRINT "DISCONNECT ERROR: ":ECODE
          END
          FAILED = 1
        END
     END ELSE
        CRT "CAN'T CONNECT - ERROR: ":ECODE
     END
END

Server Program

The program FGET is started as a server by FPUT. (It must therefore be cataloged on the account CHRIS on remote system RSYS to which connection is made by FPUT.) It accepts the connection then transmits a series of values to FPUT before disconnecting. See below.

1 CRT 'ACCEPTING...':
  ACCEPT "FGET" TO S TIMEOUT 2 SETTING ECODE ELSE
    PRINT 'CONNECT ERROR : ' : ECODE
    STOP
  END
  CRT 'ACCEPTED.'
  !
  DT = STR("+",2000)
  FOR X = 1 TO 100
    SEND CHAR(X):DT ON S SETTING ECODE ELSE
      CRT "SEND ERROR = ":ECODE
100   DISCONNECT S SETTING ECODE ELSE
        PRINT 'DISCONNECT ERROR : ' : ECODE
      END
    STOP
    END
  NEXT X
  !
  SEND "END" ON S SETTING ECODE ELSE
    CRT "SEND ERROR = ":ECODE
    GOTO 100
  END
2 DISCONNECT S SETTING ECODE ELSE
    PRINT 'DISCONNECT ERROR : ' : ECODE
    STOP
  END
  GOTO 1
  END

PERFORM Client/Server

Note

The code for these example programs can be found in the file /SYSPROG/BP.

Client Program

The program PERFORM-CLIENT connects to a system and an account selected by the user, and starts program PERFORM-SERVER on that system/account. A command entered by the user is sent to PERFORM-SERVER. The response from PERFORM-SERVER is displayed. This continues until END is entered, disconnecting the link. See below.

   AM = CHAR(254)
   PRINT 'SYSTEM ':
   INPUT SYS
   PRINT 'ACCOUNT ':
   INPUT ACCN
   CRT 'CONNECTING...':
   CONNECT SYS : AM : ACCN : AM : "PERFORM-SERVER" TO S TIMEOUT 1...
   SETTING ECODE ELSE GOTO 99
   CRT 'OK.'
1  INPUT COMMAND
   IF COMMAND='END' THEN GOTO 88
   SEND COMMAND ON S SETTING ECODE ELSE GOTO 71
   RECWAIT STUFF FROM S SETTING ECODE ELSE GOTO 72
*  CRT STUFF
   N = COUNT(STUFF,AM)+1
   FOR I=1 TO N
      CRT STUFF<I>
   NEXT I
   GOTO 1
71 CRT 'TX ERROR - ':ECODE
   GOTO 88
72 CRT 'RX ERROR - ':ECODE
88 CRT 'DISCONNECTING...':
   DISCONNECTING S SETTING ECODE ELSE GOTO 99
   CRT 'OK.'
   STOP
99 CRT "ERROR - ":ECODE
   END

Server Program

This program is started following a request from PERFORM-CLIENT. It accepts the connection, then waits to receive input from PERFORM-CLIENT.

The data from PERFORM-CLIENT should be a command. PERFORM-SERVER executes the command via a PERFORM statement and sends the result to PERFORM-CLIENT (which displays the result on the local terminal). This continues until PERFORM-CLIENT terminates the session. See below.

   CRT 'ACCEPTING...':
   ACCEPT "PERFORM-SERVER" TO S TIMEOUT 1 SETTING ECODE ELSE GOTO 99
   CRT 'OK.'
1  CRT '?':
   RECWAIT X FROM S SETTING ECODE ELSE GOTO 71
   CRT X
   PERFORM X CAPTURING Y
   SEND Y ON S SETTING ECODE ELSE GOTO 72
   GOTO 1
71 CRT 'RX ERROR - ':ECODE
   GOTO 88
72 CRT 'TX ERROR - ':ECODE
88 CRT 'DISCONNECTING...':
   DISCONNECT S SETTING ECODE ELSE GOTO 99
   CRT 'OK.'
   STOP
99 CRT 'ERROR - ':ECODE
   END

TCP Client/Server

The following client and server programs demonstrate how you might use TCP/IP to communicate between two Reality systems.

Note

The code for these example programs can be found in the file /SYSPROG/BP.

Client Program

* RTCL - Execute remote TCL command
*
* RTCL Host|* TclCommand  -- * for localhost"
*
* Connect to XTCL listening on HostName, port 52002, send TCL 
* command which XTCL will PERFORM and return the response.
*
* Set environment variable RNWS_LOG_LEVEL in range 0 to 7 for
* tracing. Set environment variable RNWS_LOG_FILE to specify trace
* file.
*
   EQU EDISCONN TO 4235
*
   TCL = SENTENCE()
   HOST = FIELD(TCL," ",2)
   CMD  = FIELD(TCL," ",3)
   PORT = 52002
*
   IF HOST = "" OR HOST = "?" THEN
      PRINT "RTCL Host|* {TclCommand}  -- * for localhost"
      STOP
   END
*
   IF HOST = "*" THEN HOST = "127.0.0.1"
   SYS="*TCP*":HOST:";port=":PORT
*
   CONNECT SYS TO SESS SETTING ERRNO ELSE
      OPN = "CONNECT"
      GOTO FIN
   END
*
   ONEOFF = CMD # ""
   LOOP
      IF NOT(ONEOFF) THEN INPUT CMD
   WHILE CMD # "" DO
      SDAT=CMD
      GOSUB SEND_SDAT
      IF ERRNO THEN GOTO FIN
      GOSUB RECV_RDAT
      IF ERRNO THEN GOTO FIN
      PRINT RDAT<1>
      IF ONEOFF THEN GOTO FIN
   REPEAT
*
FIN:
*
   IF ERRNO THEN
      IF ERRNO = EDISCONN THEN
         PRINT SYS:" disconnected"
      END ELSE
         PRINT OPN:" (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   IF NOT(UNASSIGNED(SESS)) THEN
      DISCONNECT SESS SETTING ERRNO ELSE
         PRINT "DISCONNECT (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   IF NOT(UNASSIGNED(LSESS)) THEN
      DISCONNECT LSESS SETTING ERRNO ELSE
         PRINT "DISCONNECT(listener) (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   STOP
*
**
***
SEND_SDAT:
**********
   MSG = "<":SDAT:">"
   SEND MSG TO SESS SETTING ERRNO ELSE
      OPN = "SEND"
   END
   RETURN
*
**
***
RECV_RDAT:
**********
   RDAT = ""
   LOOP
      RECEIVE MSG FROM SESS SETTING ERRNO ELSE
         OPN = "RECEIVE"
         GOTO FIN_RECV_RDAT
      END
      RDAT = RDAT:MSG
      AGAIN = RDAT[-1,1] # ">"
   WHILE AGAIN DO REPEAT
*
   RDAT[-1,1] = ""
   RDAT[1,1] = ""
*
FIN_RECV_RDAT:
*
   RETURN
*
**
***
END
***   

Server Program

* XTCL - Perform TCL command from remote client
*
* XTCL LocalHostName|*  -- * for all local interfaces
*
* Listen on port 52002 for incoming connection, receive and perform
* TCL command and return response to client. Loop back for next 
* command or client disconnect. 'RTCL host OFF' will log us off.
*
* Set environment variable RNWS_LOG_LEVEL in range 0 to 7 for tracing.
* Set environment variable RNWS_LOG_FILE to specify trace file.
*
   EQU EDISCONN TO 4235
*
   LOOPING = 0
   TCL = SENTENCE()
   HOST = FIELD(TCL," ",2)
   PORT = 52002
*
   IF HOST = "" OR HOST = "?" THEN
      PRINT "XTCL LocalHostname|*  -- * for all local interfaces"
      STOP
   END
*
   IF HOST = "*" THEN
      HOST = ""
   END
*
   SYS="*TCP*":HOST:";port=":PORT
*
*  Activate listening socket
*
   ACCEPT SYS:";listen=1" TO LSESS SETTING ERRNO ELSE
      OPN = "ACCEPT(listen)"
      GOTO FIN
   END
*
* Accept incoming connection
*
   LOOPING = 1
   ERRNO = 0
   LOOP
      ACCEPT SYS TO SESS SETTING ERRNO ELSE
         OPN = "ACCEPT"
         GOTO FIN
      END
*
      LOOP
         ERRNO = 0
         GOSUB RECV_RDAT
         IF ERRNO = 0 THEN
            IF RDAT = "STOP" THEN
               SDAT = "CLOSING SERVER"
               LOOPING = 0
            END ELSE
               PERFORM RDAT CAPTURING SDAT
            END
            GOSUB SEND_SDAT
         END
      WHILE ERRNO = 0 DO REPEAT
      IF ERRNO = EDISCONN THEN ERRNO=0
*
FIN:
*
      IF ERRNO THEN
         PRINT OPN:" (":SYS:") failed, ERRNO=":ERRNO
      END
*
      IF NOT(UNASSIGNED(SESS)) THEN
         DISCONNECT SESS SETTING ERRNO ELSE
            PRINT "DISCONNECT (":SYS:") failed, ERRNO=":ERRNO
         END
      END
*
   WHILE ERRNO = 0 AND LOOPING DO REPEAT
*
   IF NOT(UNASSIGNED(LSESS)) THEN
      DISCONNECT LSESS SETTING ERRNO ELSE
         PRINT "DISCONNECT(listener) (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   STOP
*
**
***
SEND_SDAT:
**********
   MSG = "<":SDAT:">"
   SEND MSG TO SESS SETTING ERRNO ELSE
      OPN = "SEND"
   END
   RETURN
*
**
***
RECV_RDAT:
**********
   RDAT = ""
   LOOP
      RECEIVE MSG FROM SESS SETTING ERRNO ELSE
         OPN = "RECEIVE"
         GOTO FIN_RECV_RDAT
      END
      RDAT = RDAT:MSG
      AGAIN = RDAT[-1,1] # ">"
   WHILE AGAIN DO REPEAT
*
   RDAT[-1,1] = ""
   RDAT[1,1] = ""
*
FIN_RECV_RDAT:
*
   RETURN
*
**
***
END
***

TCP Ping

Note

The code for this example program can be found in the file /SYSPROG/BP.

* PING - Reality sockets test program to send/receive to echo server
*
* PING HostName
*
* Provides similar functionality to the UNIX ping utility. A
* connection is established to a remote TCP 'echo' server specified
* by argument 1. A message is sent and echo received, five times.
*
* Set environment variable RNWS_LOG_LEVEL in range 0 to 7 for
* tracing. Set environment variable RNWS_LOG_FILE to specify trace
* file.
*
   TCL = SENTENCE()
   HOST = FIELD(TCL," ",2)
   PORT=7
*
   IF HOST = "" OR HOST = "?" THEN
      PRINT "PING [HostName|IP address]"
      STOP
   END
*
   SYS="*TCP*":HOST:";port=":PORT
*
   CONNECT SYS TO SECHO TIMEOUT 1 SETTING ERRNO ELSE
      PRINT "Failed to connect to ":SYS:", error=":ERRNO
      GOTO L_ABORT
   END
*
   SMSG="Data/Basic socket call to echo port"
*
   FOR I = 1 TO 5
      SEND SMSG TO SECHO SETTING ERRNO ELSE
         PRINT "Failed to send msg to ":SYS:", error=":ERRNO
         GOTO L_ABORT
      END
*
      RECWAIT RMSG FROM SECHO TIMEOUT 1 SETTING ERRNO ELSE
         PRINT "Failed to receive msg from ":SYS:", error=":ERRNO
         GOTO L_ABORT
      END
      PRINT "Received:'":RMSG:"' from ":HOST
   NEXT I
*
L_ABORT:
   IF NOT(UNASSIGNED(SECHO)) THEN
      DISCONNECT SECHO SETTING ERRNO ELSE
         PRINT "Failed to disconnect from ":SYS:", error=":ERRNO
      END
   END
*
   STOP
*
END

TCP Email

Note

The code for this example program can be found in the file /SYSPROG/BP.

* MAIL - send email
*
* MAIL <To:> <From:> <SmtpServer>
*
* Connect to SMTP service on specified DNS name and pass sender and
* recipient's mail path, viz email addresses, finally send the email
* body.
*
* This program was based on information found in RFC 821, it in no
* way handles every eventuality and is for example purposes only.
*
   EQU EDISCONN TO 4235
   EQU TRACING TO 1
   CRLF = CHAR(13):CHAR(10)
   EML.HOST = "mysystem.northgate-is"
*
   TCL = SENTENCE()
   EML.TO   = FIELD(TCL," ",2)
   EML.FROM = FIELD(TCL," ",3)
   SMTPHOST  = FIELD(TCL," ",4)
   PORT = 25
*
   IF SMTPHOST = "" THEN
      PRINT "MAIL <To:> <From:> <SmtpServer>"
      STOP
   END
*
   SYS="*TCP*":SMTPHOST:";port=":PORT
*
*  Build email header
*
   EML.HEADER = "Subject: TEST Sockets API":CRLF:"From: ":EML.FROM
   EML.HEADER = EML.HEADER:CRLF:"To: ":EML.TO
*
   PRINT "Enter message text:"
   INPUT EML.TEXT
*
   CONNECT SYS TO SESS SETTING ERRNO ELSE
      OPN = "CONNECT"
      GOTO FIN
   END
*
   SDAT = "HELO ":EML.HOST
   GOSUB POST; IF ERRNO THEN GOTO FIN
   IF RDAT[1,6] # "250 OK" THEN GOTO PROTOCOL.ERROR
*
   SDAT = "MAIL FROM:":EML.FROM
   GOSUB POST; IF ERRNO THEN GOTO FIN
   IF RDAT[1,6] # "250 OK" THEN GOTO PROTOCOL.ERROR
*
   SDAT = "RCPT TO:":EML.TO
   GOSUB POST; IF ERRNO THEN GOTO FIN
   IF RDAT[1,6] # "250 OK" THEN GOTO PROTOCOL.ERROR
*
   SDAT = "DATA"
   GOSUB POST; IF ERRNO THEN GOTO FIN
   IF RDAT[1,3] # "354" THEN GOTO PROTOCOL.ERROR
*   
*  Build email body - header, blank line, text & a '.' on it's own line,
*  this, with the final CRLF added by POST, terminates the message body.
*
   SDAT = EML.HEADER:CRLF:CRLF:EML.TEXT:CRLF:"."
   GOSUB POST; IF ERRNO THEN GOTO FIN
   IF RDAT[1,6] # "250 OK" THEN GOTO PROTOCOL.ERROR
*
   GOTO FIN
*
PROTOCOL.ERROR:
*
   PRINT "Unexpected response from SMTP server."
   PRINT "SDAT=":SDAT
   PRINT "RDAT=":RDAT
*
FIN:
*
   IF ERRNO THEN
      IF ERRNO = EDISCONN THEN
         PRINT SYS:" disconnected"
      END ELSE
         PRINT OPN:" (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   IF NOT(UNASSIGNED(SESS)) THEN
      DISCONNECT SESS SETTING ERRNO ELSE
         PRINT "DISCONNECT (":SYS:") failed, ERRNO=":ERRNO
      END
   END
*
   STOP
*
**
***
POST:
*****
   IF TRACING THEN PRINT "POST:":SDAT
   MSG = SDAT:CRLF
   SEND MSG TO SESS SETTING ERRNO THEN
      GOSUB RECV
   END ELSE
      OPN = "SEND"
   END
   RETURN
*
**
***
RECV:
****
   RDAT = ""
   LOOP
      RECEIVE MSG FROM SESS SETTING ERRNO ELSE
         OPN = "RECEIVE"
         GOTO FIN_RECV
      END
      RDAT = RDAT:MSG
      AGAIN = RDAT[-2,2] # CRLF
   WHILE AGAIN DO REPEAT
*
   RDAT[-2,2] = ""
*
   IF TRACING THEN PRINT "RECV:":RDAT
   IF RDAT[1,3] = "220" THEN
*     This is the greeting reply from server, is ok, go do another receive
      GOTO RECV
   END
FIN_RECV:
*
   RETURN
*
**
***
END
***