Using Socket Connections

Using Socket Connections

Top  Previous  Next

 

QMBasic includes a set of functions that allow network connections to be established with other systems or other software on the same system. Where supported by the underlying operating system, both IPV4 and IPV6 protocol connections are available though IPV6 support must be enabled using the IPV6 configuration parameter.

 

 

What is a Socket?

 

A socket is one end of a bidirectional link between two software components across a network or within the same system. A socket is established using a network address (typically written as four dot separated values such as 193.118.13.11 for IPV4 addresses or up to eight colon separated hexadecimal values for IPV6 addresses) and a port number. These can be considered as being much like a telephone number and extension. The network address identifies the device on the network to which a connection is to be established and the port number identifies a service within that device.

 

Just as we can look up a telephone number in a directory, so the internet has domain name servers that fulfil the same role, allowing users to reference a network destination by name instead of its number. For example, www.openqm.com translates to 81.31.112.103.

 

There is a central registry of standard port numbers (e.g. 23 for a telnet connection or 80 for a web server) but these are not rigidly enforced. By default, QM uses ports 4242 and 4243 for terminal and QMClient connections respectively. On Unix and Linux systems, only processes running as root can listen on port numbers less than 1024.

 

QM also supports Unix domain sockets on Unix and Linux systems. These use a file system pathname for the connection in place of a network address.

 

A socket may be established in two modes; stream and datagram. A stream connection represents a channel over which a succession of messages may be passed in each direction until connection is broken by one participant. A datagram connection consists of a single message and response pair after which the connection is broken.

 

There are also two protocols used to manage the internal structure of a message; TCP (transmission control protocol) and UDP (user datagram protocol). These are usually paired up with the corresponding socket modes such that TCP is used over stream connections and UDP over datagram connections but the underlying network system allows variations.

 

 

Socket Programming for Stream Connections

 

Creating a stream connection is very easy. Although one end of this connection is likely to be something other than a QMBasic program, the example below is based on two QMBasic programs communicating across a network.

 

The server process (the one that is waiting for an incoming connection) starts listening by using a sequence of the form:

 

SRVR.SKT = CREATE.SERVER.SOCKET("", 4000, SKT$STREAM + SKT$TCP)

IF STATUS() THEN STOP 'Cannot initialise server socket'

SKT = ACCEPT.SOCKET.CONNECTION(SRVR.SKT, 0)

IF STATUS() THEN STOP 'Error accepting connection'

 

The CREATE.SERVER.SOCKET() call listens for incoming TCP stream connections on port 4000. The first argument in this function has been specified as a null string to listen on all local addresses. A specific address can be specified if required. SRVR.SKT becomes a socket variable that is effectively monitoring for incoming connections. This is then used in a call to ACCEPT.SOCKET.CONNECTION() which will wait for a connection to arrive. A timeout period can be specified. On return from this function, if no error has occurred, SKT will be the socket variable for the specific connection. The program could resume listening for further incoming connections using ACCEPT.SOCKET.CONNECTION().

 

Once a connection has been established, data can be read or written using READ.SOCKET() and WRITE.SOCKET().

 

DATA = READ.SOCKET(SKT, 100, SKT$BLOCKING, 0)

N = WRITE.SOCKET(SKT, DATA, 0, 0)

 

This example simply echoes the data back to the other end of the connection. The SKT$BLOCKING flag specifies that READ.SOCKET() should wait for incoming data rather than returning immediately if there is no data waiting. Note that this waits for any data, not the full 100 bytes specified as the limit. It may be necessary to perform several reads to assemble the data sent from the other end of the connection.

 

Finally, the connection can be terminated using CLOSE.SOCKET(), remembering that the server socket also needs to be closed when no new connections are to be handled. Like all QMBasic variables, if a program terminates and a socket variable is discarded, the socket will be closed automatically.

 

CLOSE.SOCKET SKT

CLOSE.SOCKET SRVR.SKT

 

The client program that wants to connect to this server would be of the form

 

SKT = OPEN.SOCKET("172.16.13.14", 4000, SKT$BLOCKING)

IF STATUS() THEN STOP 'Cannot open socket'

N = WRITE.SOCKET(SKT, DATA, 0, 0)

REPLY = READ.SOCKET(SKT, 100, SKT$BLOCKING, 0)

CLOSE.SOCKET SKT

 

 

Datagram Connections

 

A datagram connection is typically used to send a single message to a server which returns a single response and then closes the connection. The domain name servers mentioned above use this form of data exchange when looking up a name.

 

The server program becomes simply:

 

SKT = CREATE.SERVER.SOCKET("", 4000, SKT$DGRM + SKT$UDP)

IF STATUS() THEN STOP 'Cannot initialise socket'

DATA = READ.SOCKET(SKT, 100, SKT$BLOCKING, 0)

N = WRITE.SOCKET(SKT, DATA, 0, 0)

CLOSE.SOCKET SKT

 

The client program becomes:

 

SKT = OPEN.SOCKET("172.16.13.14", 4000, SKT$DGRM + SKT$UDP + SKT$BLOCKING)

IF STATUS() THEN STOP 'Cannot open socket'

N = WRITE.SOCKET(SKT, DATA, 0, 0)

REPLY = READ.SOCKET(SKT, 100, SKT$BLOCKING, 0)

CLOSE.SOCKET SKT

 

 
Socket Tracing

 

Socket tracing enables an application to create a diagnostic report of all data transmitted in either direction via a socket. Tracing is enabled by use of the SKT$INFO.TRACE mode of the SET.SOCKET.MODE() function, passing in the pathname of the log file to be created. Any existing data in this log file will be overwritten. Tracing remains active until the socket is closed or a further call to SET.SOCKET.MODE() sets the log file pathname to a null string.

 

The log consists of a simple text file in with entries tagged as SEND or RECV to indicate the direction of data transfer. Each entry shows the data in both hexadecimal and character form.

 

 
Working with Multiple Simultaneous Socket Connections

 

If an application needs to work with multiple simultaneous socket connections, handling input on each socket as it occurs, the SELECT.SOCKET() function provides a way to wait for an event on any of a collection of sockets.

 

 

Socket Inheritance

 

As an alternative to working with multiple simultaneous socket connections, it is sometimes useful for a network listener process to use ACCEPT.SOCKET.CONNECTION() to wait for and accept an incoming connection and then to start a separate phantom process to handle that connection. This can be achieved using socket inheritance.

 

After accepting the incoming connection, the server should set the socket into inheritable mode using the SKT$INHERITABLE mode of the SET.SOCKET.MODE() function. Then, when the process starts a phantom, the inheritable socket is passed to the new process via the @SOCKET variable, closing it in the server process. Only a single socket can be put into inheritable mode at one time. Attempting to set inheritability on a second socket will cancel the inheritability of the previous socket. Also, note that server mode sockets opened with CREATE.SERVER.SOCKET() cannot be made inheritable.

 

A phantom process that inherits a socket from its parent process consumes a licence as it is considered to be an interactive process.

 

 

 

See also:

ACCEPT.SOCKET.CONNECTION, CLOSE.SOCKET, CREATE.SERVER.SOCKET(), OPEN.SOCKET(), READ.SOCKET(), SELECT.SOCKET(), SERVER.ADDR(), SET.SOCKET.MODE(), SOCKET.INFO(), WRITE.SOCKET()