说在前面
- 环境: WSL、ubuntu16
- 参考: UNIX网络编程
- 目录:这里
- 其他:接上篇⟅UNIX网络编程⟆⦔TCP客户/服务器程序实例(一)
目的
- 通过本例来了解服务器和客户是如何启动、终止的,更重要的是了解在发生错误(例如客户主机崩溃、客户进程崩溃、网络连接断开)时将会发生啥。
正常启动
-
启动服务器
root@xxx:./server.out
随后,服务器将调用socket、bind、listen和accept,并且阻塞于accept。假设此时还未启动任何客户端。
-
使用netstat命令
注意:目前WSL不支持netstat命令,所以实验放到ubuntu上吧,详见issue打开另一个终端,并键入命令:
netstat -a
可以看到结果中Local Address一列中端口号为9877的即为服务器程序。9877前面为 ,表示通配地址。Foreign Address为 ,表示可接受任意地址和端口的外来连接。State一列为LISTEN,表示该套接字处于LISTEN状态。 -
启动客户端
这里我们选择在同一台主机上运行客户端程序。使用环回地址127.0.0.1。root@xxx:./client.out 127.0.0.1
随后,客户端调用socket和connect,后者引起TCP三路握手。
当三路握手完成后,服务器accept调用返回,客户端connect调用返回。此时,客户端进入str_cli函数(见上节),等待获取标准输入(fgets函数)。
服务器accept返回后,将调用fork产生子进程,子进程调用str_echo。在str_echo函数中,由于此时客户还未发送数据,将阻塞在读取(即read调用)。同时,父进程在fork返回后,再次调用accept,等待新的客户端连接。 -
再次使用netstat命令
netstat -a | grep 9877 # grep 9877表示抽取含有9877的项
可以看到有两个已连接的套接字,第一个的端口号为9877,即服务器子进程的套接字;第二个的端口号为44828,对应客户端的套接字。
使用ps命令ps -t pts/* -o pid,ppid,tty,stat,args,wchan
下面为 分别为2和18的输出,分别表示不同的终端号(我打开了俩终端,一个运行server,一个运行client)。WCHAN表示wait channel,表示当进程sleep 时,kernel 当前运行的函数。这里的值和书上的不同,可能有操作系统版本不同的原因。
-
客户端标准输入
在我们输入“hello world!”后,屏幕上立即输出了另一行“hello world!”。
该过程可以描述为:客户fgets获取标准输入→客户write发送数据→服务器read接收数据→服务器write回送数据→客户read接收数据→客户fputs标准输出。
正常终止
-
客户端键入Ctrl+D,表示EOF字符
客户:在输入EOF后,函数fgets返回空指针,str_cli返回;
客户:str_cli返回后,进入main函数,调用exit终止进程;
客户:进程终止时,会关闭该进程打开的所有描述符,因此客户套接字被关闭,客户TCP发送FIN给服务器;
服务器:服务器响应ACK,并进入CLOSE_WAIT状态。
客户:接收到ACK后,进入FIN_WAIT_2状态。
服务器:服务器在接收FIN时,readline返回0,函数str_echo返回;并在main函数中调用exit终止进程。
服务器:服务器关闭相应的套接字,发送FIN。
客户:客户接收FIN后,返回ACK,进入TIME_WAIT状态。
服务器:接收ACK后,连接彻底关闭。
(上述服务器指服务器子进程)
-
使用netstat命令
可以看到,相关的套接字只有两个了,客户连接进入TIME_WAIT状态,服务器主进程(父进程)依然是LISTEN状态,服务器子进程关闭。 -
使用ps命令
在服务器子进程终止时,会给父进程发送一个SIGCHILD信号,在本节中并没有处理该信号。由于父进程没有处理该信号,子进程变成僵尸进程,使用ps命令可以看到defunct标志。
处理僵尸进程
- 见下一节