Serving Multiple Concurrent Connections

A single server process can practically only serve one client process at a time. If another client tries to connect while the server is busy servicing another request, that client will receive an error during the connection attempt, such as "connection refused". If the duration of your typical server session is very short, and there are not too many clients competing to access it, then the easiest way to handle the possibility of a busy server is by having the clients retry their connection attempt after a short wait. However, if the average connection time is long, or the number of competing clients is sufficiently large, this will not give very good results, and a more powerful mechanism of supporting multiple clients is needed.

The "normal" way to implement concurrent connection servers in the UNIX world is by forking a new child process to handle each new connection. The typical Telnet daemon is an example of this. It listens on port 23 for a client connection, and as soon as it accepts one, it forks a child, which inherits (and takes over) the connection, while the original server closes its copy of the connection and goes back to listening for the next client connection.

The TCP subroutines do not internally provide such a forking mechanism. There are, however, workarounds to this obstacle (including using SUBMIT to fork your own child-server).

One such workaround would be to launch multiple servers, each listening on a different port. The clients would then need to be capable of iteratively trying to connect at each of the specified port numbers until a successful connection was established. A variation of this technique would be for the server, upon accepting a connection, to launch a child instance of A-Shell, passing it a command line which launched a new copy of the server at a new port number. At the same time, it would return to the client this port number so that the client could disconnect and then reconnect at the new port number.

Multiple independent processes cannot all be listening on the same port, which is why the scheme just described requires separate port numbers. (Technically, there is a way around that restriction, but it involves opening a listening socket in one process, and then spawning multiple children that inherit and share the listening socket. This is beyond our scope at this point.)

A more sophisticated workaround is for the server, after accepting a connection, to "hand it off" to a child process to be serviced, thus freeing the server to immediately accept another connection. You can do this using SUBMIT.SBR or AMOS.SBR. You would need to pass to the child process (via command line arguments) the name of the program to service the connection and most importantly the socket number.

Sockets are inherited by child processes launched via SUBMIT, but the child process needs to be told the socket number so it can pass it to TCPSRV.SBR.

Such a server program would contain code something like this:

LOOP:

   SOCKPORT = MY'PORT                         ! specified listening port

   call ACCEPT'CONNECTION                     ! wait for connection

                                              ! (return STATUS, SOCKPORT)

   if STATUS <= 0 goto ERROR

                                              ! Now pass socket to child…

   xcall AMOS,"SUBMIT CHILD "+str(SOCKPORT)   ! launch CHILD.CTL

                                              ! (inherits our socket)

   call CLOSE'CONNECTION                      ! close our copy of socket

   goto LOOP                                  ! wait for another client

 

The CHILD.CTL file might look something like this:

;CHILD.CTL
RUN CHILD $0

A-Shell supports DO-file style arguments passed to CTL files via SUBMIT.

The child.bas program might look something like this:

SOCKPORT = str(CMDLIN)         ! (BASICPlus) pick up socket # from cmd line

   call READ'SOCKET            ! wait for client to send us something

   <process request>

   call WRITE'SOCKET           ! send client response

   call CLOSE'CONNECTION       ! (or, we could loop back for more

                               ! read/write cycles)

END                            ! Terminate program and A-Shell session