Working with TCP/IP sockets

The OPEN# statement now supports opening TCP/IP sockets allowing a KCML program to act as either a client or a server on a TCP/IP network. The connection can also be encrypted and optionally authenticated using Secure Sockets (SSLv3 or TLS, RFC 2246). For more about SSL in KCML see this page.

TCP sockets are used to connect two named end points on a network each defined by a unique 4 byte IP address and a two byte port number. One end, called the server, listens for incoming connections while the other end, the client, initiates the stream by explicitly connecting. Once connected each end can read and write to the stream without worrying about addressing. The connection lasts until one end is explicitly closed. KCML also supports the UDP protocol for datagrams where each write must specify the receiving address and where a socket can get packets from multiple sources, each with their own sending address attached. For more about UDP in KCML see Working with UDP sockets.

Socket Client

To act as a client a program can issue an open statement such as

OPEN #1, "alpha.bigco.com:10000", "@"
hStream = OPEN "10.1.1.2:rexec", "@"

The " @" sign in the mode argument specifies that a socket is to be opened. The socket to use is specified in the filename argument with a colon separating the IP address from the port number. Port numbers may be supplied either as numbers or as known service names e.g. http or smtp. The server address may be either a numeric IP address in dot notation or a host name if there is a DNS, NIS or hosts file available to resolve this name. The OPEN will attempt to connect to that port and if there is no server available to accept the connection it will fail with a D82 error. However if a server does connect then the OPEN will succeed and the stream number may be used in subsequent $IF #, READ# and WRITE# statements to exchange data with the server. To close the connection use CLOSE #.

Socket Server

To act as a server a KCML program also issues a OPEN# statement but this time it does not have to specify the host address as this is fixed to be the address of the machine on which the program is executing. E.g.

hConnectStream = OPEN " :8123", "@"

You can restrict visibility of the socket to the local machine by either using :: rather than : before the port or you can set the LCL=Y option (see options below). If the server is multihomed and you need to bind against a specific interface then you can supply the IP address and set the SVR=Y socket option.

This OPEN# will return immediately but the network layer will be primed to receive connections. You can check for a connection by using a $IF # e.g.

a = $IF #hConnectStream, timeout

The $IF # will return 1 in this example whenever a client connects and zero if the timeout expires. The stream in this form of the statement is reserved for connecting only and once a connection arrives the server program must then issue another OPEN# to get the socket to use for sending messages. To get a working socket from a connection pass the initial stream as the filename in a further OPEN# thus

hMsgStream = OPEN $PRINTF("#%d", hConnectStream), "@"

And then use #MsgStream in $IF #, READ#, WRITE# and CLOSE #. The connection stream should only be used in $IF # and CLOSE #. Acting as a server requires more careful programming if a service has to be provided for more than one client at a time. The message streams should be allocated from a pool array and a $IF # used to test for messages that need to be processed. A READ# will block if no data is available so use $IF # first to see if there is any data available. The work done for each message should be completed as quickly as possible in order to provide a timely response for other clients.

Note: server programs on NT require KCML 6.0 or greater and Winsock version 2 to use $IF to test for an available connection. KCML 5.02 only supported Winsock version 1.1 which provides no way to test for an accepted incoming connection and so a $IF #hConnectStream would always return 1. Winsock version 2 ships with NT4, Windows 98 and Windows 2000. It is also available from the Microsoft web site as an patch upgrade for Windows 95.

There is a similar issue with clients testing for the presence of a server using $ALARM and OPEN# which will only work on Unix (all versions) and Windows (>= 6.0 with Winsock2). The use of a non-blocking socket and $IF is also possible but again requires 6.0 to work under Windows.

Socket options

Some option settings can be applied to a socket using a simple 3 letter code with either a Y or N value. Thus to enable SSL on a socket you can add the SSL=Y clause immediately after the host and port as in:

h = OPEN "www.kcml.com:https,SSL=Y","@"

Multiple options are possible but they must be comma separated. Available options are listed in the following table. Options are assumed off, i.e. set to N, by default unless specified otherwise in the table.

Option codePurpose
LCLBind server socket to the localhost address 127.0.0.1 so that it cannot be accessed from another machine. This can be useful when both the socket server and client are on the same machine. It increases security and avoids issues with software firewalls on Windows.
SVRAct as a server binding against a specific IP address. Useful if the server has multiple interfaces and you wish to listen on only one of them.
RUAReuse the port. By default this is on and will allow KCML to immediately reuse a server port even if the previous connection has not yet cleaned up.
SSLEnable SSL secure sockets
VERForce verification of the SSL server certificate. Connection will fail if not valid.
LIMLimit the ciphers allowed in the SSL handshake. This will prevent the use of anonymous DH, MD5 or keys less than 56 bits.

Example socket client program

An example of the use of sockets in a client application would be a program that uses the SMTP protocol to send an email message across the Internet. Simple Mail Transfer Protocol (SMTP) defined in RFC821 is the protocol used to transfer email across the internet. If you need to extend this to handle attachments you may find the MIME RFC2045 etc. relevant.

REM simple STMP Internet mail program showing use of sockets
REM A real program would handle errors better
DIM msg$80
DIM CRLF$=HEX(0D0A)
REM specify the SMTP host and protocol
DIM MailHost$="mail.someisp.com:smtp"
REM identify our machine
DIM OurHostname$="me.myco.com"
REM message sender and recipient
DIM To$="[email protected]"
DIM From$="[email protected]"
DIM subject$="Fetching a pail of water"
REM connect to some SMTP host
OPEN #1, MailHost$, "@"
REM set line mode for replies
STR($OPTIONS #1, 2, 1) = HEX(07)
REM get the welcome msg
IF ('recv() == TRUE)
REM say hello to start a conversation
msg$ = "HELO " & OurHostname$ & CRLF$
'send(msg$)
IF ('recv() == TRUE)
REM send who msg is from in RFC822 format <[email protected]>
msg$ = "MAIL FROM: <" & From$ & ">" & CRLF$
'send(msg$)
IF ('recv() == TRUE)
REM send who it is for in that format, this can be repeated and
REM may be rejected if no such user and host cannot forward
msg$ = "RCPT TO: <" & To$ & ">" & CRLF$
'send(msg$)
IF ('recv() == TRUE)
REM now send the message ending with a dot on a line of its own
msg$ = "DATA" & CRLF$
'send(msg$)
IF ('recv() == TRUE)
REM the RFC822 header which ends in a blank line
msg$ = "To: " & To$ & CRLF$ & "From: " & From$ & CRLF$
'send(msg$)
msg$ = "Subject: " & subject$ & CRLF$ & CRLF$
'send(msg$)
REM send body of message
REM a real program should escape singleton dots and the word From
FOR i = 1 TO 5
msg$ = "hello " & $FMT("###", i) & CRLF$
'send(msg$)
NEXT i
REM end
msg$ = "." & CRLF$
'send(msg$)
IF ('recv() == TRUE)
REM we are done
msg$ = "QUIT" & CRLF$
'send(msg$)
'recv()
END IF
END IF
END IF
END IF
END IF
END IF
CLOSE #1
END
DEFSUB 'send(msg$)
a = WRITE #1, RTRIM(msg$)
RETURN a
END SUB
DEFSUB 'recv()
REM read responses in lines
REM return mail error code, 2=OK, 3=info
LOCAL DIM buf$256, count, n, cont
cont = FALSE
WHILE TRUE DO
count = READ #1, buf$
IF (count <= 3) THEN RETURN
PRINT STR(buf$,, count)
REM get reply code and check for continuation lines
IF (NOT cont) THEN CONVERT STR(buf$,, 3) TO n
IF (STR(buf$, 4, 1) <> "-") THEN BREAK
cont = TRUE
WEND
n = INT(n / 100)
RETURN (n == 2 OR n == 3 ? TRUE : FALSE)
END SUB

Example socket server program

This simple server listens on the socket provided on the command line and replies with an upper case version of what was sent.

00010 $COMPLIANCE 3
    : REM Simple socket server that replies with an upper cased version of the request that was sent
    : 'main()
    : $END
    : DEFSUB 'main()
    : LOCAL DIM nPort AS integer
    : LOCAL DIM hListenSkt AS stream
    : LOCAL DIM hConnSkt AS stream
    : LOCAL DIM sArg$0
    : LOCAL DIM n AS integer
    : LOCAL DIM nRead AS integer
    : LOCAL DIM nWrite AS integer
    : LOCAL DIM sReadBuf$1024
    : LOCAL DIM sReplyBuf$0
    : REDIM sarg$ = $ARG(1)
    : nport = CNUM(sarg$)
    : REM Ports below 1024 are usually reserved for system services and require super-user privileges
    : IF (nport > 1024)
    :     REM Open a listening socket on the specified port ...
    :     hlistenskt = OPEN $PRINTF(":%d",nport),"@"
    :     IF (hlistenskt)
    :         n = 1
    :         $PSTAT = "SockSvr"
    :         WRITE LOG _log_information, $PRINTF("Listening for requests on port %d ...", nPort)
    :         WHILE (n > 0) DO
    :             REM ... and wait for requests.
    :             n = $IF #hlistenskt,0
    :             IF (n)
    :                 REM We have a request, read it ...
    :                 hconnskt = OPEN $PRINTF("#%d",hlistenskt),"@"
    :                 IF (hconnskt)
    :                     REM ... and respond
    :                     nread = READ #hconnskt,sreadbuf$
    :                     IF (nread)
    :                         REDIM sreplybuf$ = $UPPER(STR(sreadbuf$,,nread))
    :                         nwrite = WRITE #hconnskt,sreplybuf$
    :                         IF (nwrite <> nread)
    :                             n = 0
    :                         END IF
    :                     ELSE
    :                         n = 0
    :                     END IF
    :                     REM Conversation has ended so we can now close the connected socket
    :                     CLOSE #hconnskt
    :                 ELSE
    :                     n = 0
    :                 END IF
    :             END IF
    :         WEND
    :         WRITE LOG _log_information,"Finished"
    :     ELSE
    :         WRITE LOG _log_error,$PRINTF("Failed to list on port %d, %s",nport,$ERR)
    :     END IF
    : ELSE
    :     WRITE LOG _log_error,$PRINTF("Bad port %s",sarg$)
    : END IF
    : END SUB

The server is started by specifying an unused port number as a command line parameter.

$ kcml -p sockserver.src 8000 &

Multiple requests can then be sent by using the nc or ncat utilities as a client:-

$ echo Hello World | nc localhost 8000
HELLO WORLD
$ echo Quick brown fox | nc localhost 8000
QUICK BROWN FOX

The server can be stopped either by killing it or by sending an empty request:-

$ nc localhost 8000 < /dev/null

See also:

OPEN#, CLOSE#, READ#, WRITE#, $IF, $OPTIONS# $PRINTF(
Secure sockets with SSL
Working with UDP sockets