|Powered by QM on a Linux server|
KnowledgeBase 00023: Using Multi-Threaded Sockets
QM has support for socket based connections to other systems or other processes on the same system. This can be an incoming (server) connection or an outgoing (client) connection. The application at the remote end of the connection does not need to be QM but might be, for example, a web server.
Socket connections may be persistent TCP (Transmission Control Protocol) sessions with the two systems interchanging a series of messages until the socket is closed or they may use UDP (User Datagram Protocol) to send a single message and receive a response. This article references only TCP usage.
In most cases, an application opens a single connection, exchanges messages with the remote end, and then closes the connection. Sometimes it is useful to be able to open multiple sessions simultaneously, monitoring for incoming data on any of them. This article describes how to perform this multi-threading in a QM application.
Simple Socket Usage
A simple server process that waits for an incoming TCP connection on port 4000, processes a series of messages, and then terminates the connection can be constructed as below.
srvr.skt = create.server.socket('', 4000) if status() then stop 'Cannot initialise server socket' loop skt = accept.socket.connection(srvr.skt, 0) if status() then stop 'Error accepting connection' done = @false loop msg = read.socket(skt, 100, SKT$BLOCKING, 0) ...processing, setting done flag to terminate session... until done repeat close.skt skt repeat
In this simple example, the inner loop handles a series of incoming messages and in a complete application would normally use WRITE.SOCKET to send a response to each message. When the done flag is set to true, the inner loop exits, the socket is closed, and the program goes back to waiting for an incoming connection.
It might be necessary for the application outlined above to handle multiple simultaneous incoming connections rather than processing them serially.
To achieve this, the application must create a dimensioned array of socket variables that are to be monitored, including the server socket on which new connections will appear. Elements of this array that are not socket variables will be ignored, simplifying the processing needed when new sockets are to be added to the list or sockets already in the list are closed.
The application uses the SET.SOCKET.MODE() function to mark the socket for event monitoring and then uses SELECT.SOCKET() to wait for an event on any of the monitored sockets.
equate MAX.SOCKETS to 10 dim skt(MAX.SOCKETS) * Open server socket to monitor for new connections, setting read monitor skt(1) = create.server.socket('', 4000) if status() then stop 'Cannot initialise server socket' if not(set.socket.mode(skt(1), SKT$INFO.EVENTS, SKT$READ.EVENT)) then stop 'Error from set.socket.mode()' end loop events = select.socket(skt, 5000) for i = 1 to MAX.SOCKETS if events<i> then ;* Must be a read event if i = 1 then ;* New connection * Find an unused table entry for j = 2 to MAX.SOCKETS if not(socket.info(skt(j), SKT$INFO.OPEN)) then skt(j) = accept.socket.connection(skt(1), 0) if not(set.socket.mode(skt(j), SKT$INFO.EVENTS, SKT$READ.EVENT)) then stop 'Error from set.socket.mode()' end exit end next j end else str = read.socket(skt(i), 1000, 0, 0) if status() then ;* Socket closed skt(i) = 0 end else ...process input... end end end next i repeat
In the above example, element 1 of the skt array is used for the server socket. A read event will be recorded for this socket whenever a new connection becomes available.
In the main processing loop, each time the SELECT.SOCKET() function returns, the program scans the dynamic array of events looking for events to process. Because this example uses only read events, there is no need to examine the actual content of this dynamic array to determine the event type.
If the event occurred on element 1 of the socket array, it is a new incoming connection. The program scans the socket array for a spare entry, saves the socket variable, and sets the read event monitor on this socket. A realistic use of multi-threaded sockets would probably send a message to the remote end on establishing the connection.
If the event occurred on an element of the socket array other than the server socket, the program reads data and processes it, probably sending a response. In this example, the session is terminated by the remote end closing the socket at which point the program sets the corresponding socket array variable to 0, effectively closing the local end of the socket and marking the array element as free.
Note that this example uses a 5 second timeout on the SELECT.SOCKET() function. This would allow program statements to be inserted into the main processing loop to handle shutdown of the server or any other operations that need to take place whilst waiting for events.
00114: Socket Inheritance