The following sample procedures show a simple interaction between a socket server (serversocket.p) and a socket client (socketclient.p). In this case, the socket server exits after handling a single connection and the socket client exits after receiving one data transmission from the server. These are event-driven examples, where the socket server only writes and the socket client only reads on their respective sockets. The methods for the callback events CONNECT and READ-RESPONSE are defined in the class definition (SocketManager.cls).

SocketManager.cls

CLASS SocketManager:

METHOD PUBLIC VOID OnReadResponse():

  VAR HANDLE readSocket.
  VAR MEMPTR clientMessage.
  VAR CHARACTER msgStr.
  
  readSocket = SELF.
  
  IF readSocket:get-bytes-available() = 0 THEN DO:
    RETURN.
  END.
  
  SET-SIZE(clientMessage) = readSocket:GET-BYTES-AVAILABLE().
  
  readSocket:READ(clientMessage, 1, GET-SIZE(clientMessage)).
  
  msgStr = GET-STRING(clientMessage, 1, GET-SIZE(clientMessage)).
  
  SET-SIZE(clientMessage) = 0.
  
  MESSAGE "Message from serverSocket: "  msgStr SKIP.

END METHOD.

METHOD PUBLIC VOID OnClientConnect ( INPUT vSocketHandle AS HANDLE):
  VAR CHARACTER vGt = "Server Response: got a client connection and this is the response message".
  VAR memptr vGB.
  
  // send message to client 
  SET-SIZE(vGB) = LENGTH(vGt) + 1.
  PUT-STRING(vGB, 1) = vGt.
  vSocketHandle:write(vGB, 1, get-size(vGB)).
  SET-SIZE(vGB) = 0.
END METHOD.

END CLASS

serversocket.p


VAR HANDLE serverSocket.
VAR SocketManager callbackObj.

// create a socket
CREATE server-socket serverSocket.

//Create an object instance of the class(es) containing the event methods
callbackObj = NEW SocketManager().

// When someone connects to this socket, run the method "onClientConnect"
serverSocket:set-callback("CONNECT", "OnClientConnect", callbackObj).
serverSocket:enable-connections("-S 44120").

// keep socket going - duration is just a single request
WAIT-FOR CONNECT of serverSocket.

serverSocket:DISABLE-CONNECTIONS().

DELETE OBJECT serverSocket.
DELETE OBJECT callbackObj.
QUIT.                   

socketclient.p


VAR HANDLE clientsocket.
VAR SocketManager callbackObj.

//create a socket for the client connection
CREATE SOCKET clientsocket.

callbackObj = NEW SocketManager().
clientsocket:set-callback("READ-RESPONSE", "OnReadResponse", callbackObj).
clientsocket:connect("-H localhost -S 44120").

IF clientsocket:CONNECTED() THEN
    MESSAGE "Client successfully connected to the server socket" SKIP.
ELSE DO:
    MESSAGE "Client failed to connect to the server socket - fatal error, test aborted" SKIP.
  QUIT.
END.

WAIT-FOR READ-RESPONSE OF clientsocket.

clientsocket:DISCONNECT().
DELETE OBJECT clientsocket.
DELETE OBJECT callbackObj.
QUIT.                            
                          

The following sample procedures show how you can transfer a database record between a socket server (i-sktsv2.p) and a socket client (i-sktcl2.p) using a raw transfer. It uses the sports2000 database as the source (server) and a copy of the sports2000 database as the target (client).

This is a simple example in which the server waits for connections indefinitely (until you press the STOP key), but always sends the same database record to the client for each CONNECT event. Note how the server uses the RAW transfer to first copy the record from the database, then to specify the size of the MEMPTR memory from which the record is written on the socket. The server stores the size of the record as the first piece of information sent to the client, followed by the record.

Note: The WAIT FOR statements in the following samples are based on simple scenarios. Real life applications you would probably return back to the existing WAIT-FOR that drives the entire application. In which case the event handler would have to be in a persistent mode and not as used in the samples below. For more information, see WAIT FOR statement.

i-sktsv2.p

/* Sends new customer record from the sports2000 database through a socket. */
DEFINE VARIABLE aOk           AS LOGICAL NO-UNDO.
DEFINE VARIABLE custNum       AS INTEGER NO-UNDO.
DEFINE VARIABLE hServerSocket AS HANDLE  NO-UNDO.
DEFINE VARIABLE len           AS INTEGER NO-UNDO.
DEFINE VARIABLE mBuffer       AS MEMPTR  NO-UNDO.
DEFINE VARIABLE r1            AS RAW     NO-UNDO.

MESSAGE "We are in the raw transfer socket server".
   
/* Create a new customer record with a unique custnum */
FIND LAST Customer NO-LOCK.
custNum = Customer.CustNum + 1.

CREATE Customer.
ASSIGN
  Customer.CustNum    = custNum
  Customer.Name       = "Jack Sprat"
  Customer.Address    = "222 Ferdinand St"
  Customer.City       = "Woburn"
  Customer.State      = "MA"
  Customer.PostalCode = "01801".

CREATE SERVER-SOCKET hServerSocket.
hServerSocket:SET-CALLBACK("CONNECT", "connProc", THIS-PROCEDURE).

/* Put customer record into a RAW variable and store in local DB. */
RAW-TRANSFER customer TO r1.
RELEASE customer.

/* Put the length of the record followed by a copy of the full record into a
   MEMPTR to send it through the socket. */
ASSIGN
  len                     = LENGTH(r1)
  SET-BYTE-ORDER(mBuffer) = LITTLE-ENDIAN
  SET-SIZE(mBuffer)       = len + 4
  PUT-LONG(mBuffer, 1)    = len
  PUT-BYTES(mBuffer, 5)   = r1.

aOk = hServerSocket:ENABLE-CONNECTIONS( "-S 3334").
MESSAGE "Enabled connections:" aOk.

DO ON STOP UNDO, LEAVE:
  WAIT-FOR CLOSE OF THIS-PROCEDURE.
END.

DELETE OBJECT hServerSocket.

PROCEDURE connProc:
  /* Connection procedure for server socket */
  DEFINE INPUT PARAMETER hSocket AS HANDLE.  /*Socket implicitly created*/
  IF VALID-HANDLE(hSocket) THEN DO: /* standard validation */
    MESSAGE "We are in the CONNECT event procedure, connProc".
    /* Send the size followed by the record that we put into the MEMPTR */
    hSocket:WRITE (mBuffer,1, GET-SIZE(mBuffer)).
    MESSAGE "Sent" hSocket:BYTES-WRITTEN "bytes containing customer record".
    hSocket:DISCONNECT().
    DELETE OBJECT hSocket.
  END.
  ELSE MESSAGE "Unable to obtain socket.".
END PROCEDURE.

In this example, the client (i-sktcl2.p) polls its socket procedurally until the data for the record is available to read. In this case, the client first waits for the size information, then waits for that number of bytes of Customer data. It also uses this size information to set the size of the MEMPTR region for reading the record off the socket. Finally, note that the client deletes the socket object and frees MEMPTR memory after it disconnects from the server.

i-sktcl2.p

/* Receives a new customer record through a socket and puts it into the 
   sports2000 DB. */
DEFINE VARIABLE hSocket AS HANDLE  NO-UNDO.
DEFINE VARIABLE mBuffer AS MEMPTR  NO-UNDO.
DEFINE VARIABLE r1      AS RAW     NO-UNDO.
DEFINE VARIABLE recLen  AS INTEGER NO-UNDO.

MESSAGE "We are in the raw transfer socket client".
   
CREATE SOCKET hSocket.
hSocket:CONNECT ("-H localhost -S 3334").
IF hSocket:CONNECTED() THEN
  MESSAGE "Connected OK".
ELSE DO:
  MESSAGE "Could not connect".
  RETURN.
END.
/* Do a blocking READ until we get the count of how long the record is to
   read. */
SET-SIZE(mBuffer)       = 4.
SET-BYTE-ORDER(mBuffer) = LITTLE-ENDIAN.   
hSocket:READ(mBuffer, 1, 4).
recLen = GET-LONG(mBuffer, 1).

/* READ again to get the full record. */
SET-SIZE(mBuffer) = 0.
SET-SIZE(mBuffer) = recLen.
hSocket:READ(mBuffer, 1, recLen).
MESSAGE hSocket:BYTES-READ "bytes read for customer record".

/* Copy the record to a RAW variable so we can put it into the DB */
r1 = mBuffer.
RAW-TRANSFER r1 TO Customer.

MESSAGE "Customer is:" Customer.CustNum Customer.Name Customer.Address
  Customer.City Customer.State Customer.PostalCode.
RELEASE Customer.
       
hSocket:DISCONNECT().
DELETE OBJECT hSocket.
SET-SIZE(mBuffer) = 0.

The next example also involves a client and server. The server, i-sktsv3.p, demonstrates how to set qsize, the length of the pending-connection queue, while enabling the server-socket for connections.

i-sktsv3.p

DEFINE VARIABLE m-string AS MEMPTR  NO-UNDO.
DEFINE VARIABLE ok       AS LOGICAL NO-UNDO.
DEFINE VARIABLE sserver  AS HANDLE  NO-UNDO.

CREATE SERVER-SOCKET sserver.
ok = sserver:SET-CALLBACK("CONNECT", "connProc").
MESSAGE "set connection procedure" ok.

/* Enable for connections and set the qsize */
ok = sserver:ENABLE-CONNECTIONS( "-S 3000 -qsize 5").
MESSAGE "enable connections" OK.

WAIT-FOR CONNECT OF sserver.

PROCEDURE connProc.
  /* connection procedure for server socket */
  DEFINE INPUT PARAMETER hsocket AS HANDLE.  /* implicitly created */

  IF VALID-HANDLE(hSocket) THEN DO: /* standard validation */
    MESSAGE "Inside connProc". 
    SET-SIZE(m-string)     = 64.
    PUT-STRING(m-string,1) = "SERVER - hello".
    ok = hsocket:WRITE (m-string,1,26).
    hSocket:DISCONNECT().
    DELETE OBJECT hSocket.
  END.
  ELSE MESSAGE "Unable to obtain socket.".
END.

The client, i-sktcl3.p, shows how to set and retrieve socket options.

i-sktcl3.p

DEFINE VARIABLE hsocket  AS HANDLE    NO-UNDO.
DEFINE VARIABLE ok       AS LOGICAL   NO-UNDO.
DEFINE VARIABLE ret      AS CHARACTER NO-UNDO.
DEFINE VARIABLE m-string AS MEMPTR    NO-UNDO.
DEFINE VARIABLE c        AS CHARACTER NO-UNDO.

CREATE SOCKET hsocket.

hsocket:CONNECT ("-H localhost -S 3000 ").
IF hsocket:CONNECTED() THEN
  MESSAGE "Connected OK".
ELSE DO:
  MESSAGE "Could not connect".  
  RETURN.
END.
/* connect succeeded */

OK  = hsocket:SET-SOCKET-OPTION("TCP-NODELAY", "TRUE").
ret = hsocket:GET-SOCKET-OPTION("TCP-NODELAY").
MESSAGE "TCP-NODELAY = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-LINGER", "TRUE, 55").
ret = hsocket:GET-SOCKET-OPTION("SO-LINGER").
MESSAGE "SO-LINGER = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-KEEPALIVE", "TRUE"). 
ret = hsocket:GET-SOCKET-OPTION("SO-KEEPALIVE").
MESSAGE "SO-KEEPALIVE = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-REUSEADDR", "TRUE").
ret = hsocket:GET-SOCKET-OPTION("SO-REUSEADDR").
MESSAGE "SO-REUSEADDR = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-SNDBUF", "40").
ret = hsocket:GET-SOCKET-OPTION("SO-SNDBUF").
MESSAGE "SO-SNDBUF = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-RCVBUF", "5000").
ret = hsocket:GET-SOCKET-OPTION("SO-RCVBUF").
MESSAGE "SO-RCVBUF = " ret.

OK  = hsocket:SET-SOCKET-OPTION("SO-RCVTIMEO", "60").
ret = hsocket:GET-SOCKET-OPTION("SO-RCVTIMEO").
MESSAGE "SO-RCVTIMEO = " ret.

WAIT-FOR READ-RESPONSE OF hsocket.
SET-SIZE(m-string) = 64.
ok = hsocket:READ(m-string,1,26).
c  = GET-STRING(m-string,1).
MESSAGE "the string = " c.
hsocket:DISCONNECT().
DELETE OBJECT hsocket.