5.12 Termination of Server
Process
We will now start our client/server and then
kill the server child process. This simulates the crashing of the
server process, so we can see what happens to the client. (We must
be careful to distinguish between the crashing of the server
process, which we are about to
describe, and the crashing of the server host, which we will describe in Section 5.14.)
The following steps take place:
-
We start the
server and client and type one line to the client to verify that
all is okay. That line is echoed normally by the server
child.
-
We
find the process ID of the server child and kill it. As
part of process termination, all open descriptors in the child are
closed. This causes a FIN to be sent to the client, and the client
TCP responds with an ACK. This is the first half of the TCP
connection termination.
-
The
SIGCHLD signal is sent to the server parent and handled
correctly (Figure 5.12).
-
Nothing happens at the client. The client TCP receives the FIN
from the server TCP and responds with an ACK, but the problem is
that the client process is blocked in the call to fgets
waiting for a line from the terminal.
-
Running netstat at this point shows the state of the
sockets.
linux % netstat -a | grep 9877
tcp 0 0 *:9877 *:* LISTEN
tcp 0 0 localhost:9877 localhost:43604 FIN_WAIT2
tcp 1 0 localhost:43604 localhost:9877 CLOSE_WAIT
From Figure 2.4, we see
that half of the TCP connection termination sequence has taken
place.
-
We
can still type a line of input to the client. Here is what happens
at the client starting from Step 1:
linux % tcpcli01 127.0.0.1
|
start client
|
hello
|
the first line that we
type
|
hello
|
is echoed correctly
here we kill the server child on the server host
|
another
line
|
we then type a second
line to the client
|
str_cli : server terminated
prematurely
|
When we type "another line," str_cli
calls writen and the client TCP sends the data to the
server. This is allowed by TCP because the receipt of the FIN by
the client TCP only indicates that the server process has closed
its end of the connection and will not be sending any more data.
The receipt of the FIN does not
tell the client TCP that the server process has terminated (which
in this case, it has). We will cover this again in Section
6.6 when we talk about TCP's half-close.
When the server TCP receives the data from the
client, it responds with an RST since the process that had that
socket open has terminated. We can verify that the RST was sent by
watching the packets with tcpdump.
-
The
client process will not see the RST because it calls
readline immediately after the call to writen and
readline returns 0 (EOF) immediately because of the FIN
that was received in Step 2. Our client is not expecting to receive
an EOF at this point (Figure 5.5) so it
quits with the error message "server terminated
prematurely."
-
When
the client terminates (by calling err_quit in Figure
5.5), all its open descriptors are closed.
What we have described also depends on the
timing of the example. The client's call to readline may
happen before the server's RST is received by the client, or it may
happen after. If the readline happens before the RST is
received, as we've shown in our example, the result is an
unexpected EOF in the client. But if the RST arrives first, the
result is an ECONNRESET ("Connection reset by peer") error
return from readline.
The problem in this example is that the client
is blocked in the call to fgets when the FIN arrives on
the socket. The client is really working with two descriptors鈥攖he
socket and the user input鈥攁nd instead of blocking on input from
only one of the two sources (as str_cli is currently
coded), it should block on input from either source. Indeed, this
is one purpose of the select and poll functions,
which we will describe in Chapter 6. When we recode the
str_cli function in Section 6.4, as
soon as we kill the server child, the client is notified
of the received FIN.
|