5.7 Normal Termination
At this point, the connection is established and
whatever we type to the client is echoed back.
linux % tcpcli01 127.0.0.1
|
we showed this line
earlier
|
hello,
world
|
we now type
this
|
hello, world
|
and the line is
echoed
|
good
bye
|
|
good bye
|
|
^D
|
Control-D is our
terminal EOF character
|
We type in two lines, each one is echoed, and
then we type our terminal EOF character (Control-D), which
terminates the client. If we immediately execute netstat,
we have
linux % netstat -a | grep 9877
tcp 0 0 *:9877 *:* LISTEN
tcp 0 0 localhost:42758 localhost:9877 TIME_WAIT
The client's side of the connection (since the
local port is 42758) enters the TIME_WAIT state (Section 2.7),
and the listening server is still waiting for another client
connection. (This time we pipe the output of netstat into
grep, printing only the lines with our server's well-known
port. Doing this also removes the heading line.)
We can follow through the steps involved in the
normal termination of our client and server:
-
When we type our
EOF character, fgets returns a null pointer and the
function str_cli (Figure 5.5)
returns.
-
When
str_cli returns to the client main function
(Figure 5.4), the
latter terminates by calling exit.
-
Part
of process termination is the closing of all open descriptors, so
the client socket is closed by the kernel. This sends a FIN to the
server, to which the server TCP responds with an ACK. This is the
first half of the TCP connection termination sequence. At this
point, the server socket is in the CLOSE_WAIT state and the client
socket is in the FIN_WAIT_2 state (Figures 2.4 and
2.5).
-
When
the server TCP receives the FIN, the server child is blocked in a
call to readline (Figure 5.3), and
readline then returns 0. This causes the str_echo
function to return to the server child main.
-
The
server child terminates by calling exit (Figure
5.2).
-
All
open descriptors in the server child are closed. The closing of the
connected socket by the child causes the final two segments of the
TCP connection termination to take place: a FIN from the server to
the client, and an ACK from the client (Figure 2.5). At this
point, the connection is completely terminated. The client socket
enters the TIME_WAIT state.
-
Finally, the SIGCHLD signal is sent to the parent when
the server child terminates. This occurs in this example, but we do
not catch the signal in our code, and the default action of the
signal is to be ignored. Thus, the child enters the zombie state.
We can verify this with the ps command.
linux % ps -t pts/6 -o pid,ppid,tty,stat,args,wchan
PID PPID TT STAT COMMAND WCHAN
22038 22036 pts/6 S -bash read_chan
17870 22038 pts/6 S ./tcpserv01 wait_for_connect
19315 17870 pts/6 Z [tcpserv01 <defu do_exit
The STAT of the child is now Z (for
zombie).
We need to clean up our zombie processes and
doing this requires dealing with Unix signals. In the next section,
we will give an overview of signal handling.
|