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
we now type this
and the line is echoed
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.