22.7 Concurrent UDP Servers
Most UDP servers are iterative: The server waits
for a client request, reads the request, processes the request,
sends back the reply, and then waits for the next client request.
But when the processing of the client request takes a long time,
some form of concurrency is desired.
The definition of a "long time" is whatever is
considered too much time for another client to wait while the
current client is being serviced. For example, if two client
requests arrive within 10 ms of each other, and it takes an average
of 5 seconds of clock time to service a client, then the second
client will have to wait about 10 seconds for its reply, instead of
about 5 seconds if the request was handled as soon as it
arrived.
With TCP, it is simple to just fork a
new child (or create a new thread, as we will see in Chapter 26) and let
the child handle the new client. What simplifies this server
concurrency when TCP is being used is that every client connection
is unique: The TCP socket pair is unique for every connection. But
with UDP, we must deal with two different types of servers:
-
First is a simple
UDP server that reads a client request, sends a reply, and is then
finished with the client. In this scenario, the server that reads
the client request can fork a child and let it handle the
request. The "request," that is, the contents of the datagram and
the socket address structure containing the client's protocol
address, are passed to the child in its memory image from
fork. The child then sends its reply directly to the
client.
-
Second is a UDP
server that exchanges multiple datagrams with the client. The
problem is that the only port number the client knows for the
server is its wellknown port. The client sends the first datagram
of its request to that port, but how does the server distinguish
between subsequent datagrams from that client and new requests? The
typical solution to this problem is for the server to create a new
socket for each client, bind an ephemeral port to that
socket, and use that socket for all its replies. This requires that
the client look at the port number of the server's first reply and
send subsequent datagrams for this request to that port.
An example of the second type of UDP server is
TFTP. To transfer a file using TFTP normally requires many
datagrams (hundreds or thousands, depending on the file size),
because the protocol sends only 512 bytes per datagram. The client
sends a datagram to the server's well-known port (69), specifying
the file to send or receive. The server reads the request, but
sends its reply from another socket that it creates and bind to an
ephemeral port. All subsequent datagrams between the client and
server for this file use the new socket. This allows the main TFTP
server to continue to handle other client requests, which arrive at
port 69, while this file transfer takes place (perhaps over
seconds, or even minutes).
If we assume a standalone TFTP server (i.e., not
invoked by inetd), we have the scenario shown in Figure 22.19. We assume that the
ephemeral port bound by the child to its new socket is 2134.
If inetd is used, the scenario involves
one more step. Recall from Figure 13.6 that most
UDP servers specify the wait-flag
as wait. In our description following Figure 13.10, we
said that this causes inetd to stop selecting on the
socket until its child terminates, allowing its child to read the
datagram that has arrived on the socket. Figure 22.20 shows the steps involved.
The TFTP server that is the child of
inetd calls recvfrom and reads the client
request. It then forks a child of its own, and that child
will process the client request. The TFTP server then calls
exit, sending SIGCHLD to inetd, which
tells inetd to again select on the socket bound
to UDP port 69.
|