在前文中讲述了Linux服务端TCP通信出现CLOSE_WAIT状态的原因,这篇文章主要通过一个实例演示它个一个“恶劣”影响:直接使服务端进程Down掉。
CentOS服务端建立监听端口
1 CentOS服务端建立监听端口
如上图所示,在虚拟机CentOS7服务器(192.168.1.178)中打开一个终端界面,建立8000端口的监听服务(PID:13035)。所用代码如下,和上一篇文章中的程序大体一样,只是多了一个SIGPIPE信号处理以及自动回复(Auto response from server.)部分。
代码如下 | 复制代码 |
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <signal.h> //Whether add a signal handle. void sig_handle( int signal ) int main( int argc, char **argv ) #if SIGNAL_HANDLE if( ( server_sockfd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) if( setsockopt( server_sockfd, SOL_SOCKET, SO_REUSEADDR, &llOpt, sizeof(llOpt) ) ) { if( bind( server_sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr) ) < 0 ) listen( server_sockfd, 5 ); sin_size = sizeof( struct sockaddr_in ); printf( "Server is listening with pid=[%d].\n", getpid() ); while(1) send( client_sockfd, "Auto response from server.", strlen("Auto response from server."), 0 ); memset( buf, 0x00, BUFSIZ ); |
2 在Linux中利用telnet命令创建一个客户端
新建一个shell脚本netstat_nap.sh,里面只包含一条有效命令netstat -nap|head -n 2;netstat -nap|grep 8000。
再打开一个Linux终端界面,然后输入命令telnet 192.168.1.177 8000作为客户端建立与服务端的TCP连接。这时执行脚本./netstat_nap.sh可以看到Linux客户端(PID:13045)和服务端(PID:13035)的TCP通信已经变成ESTABLISHED状态,效果如下图所示:
3 在Windows中利用telnet命令创建一个客户端
在Windows主机(192.168.1.110)中打开一个PowerShell终端界面,然后输入命令telnet 192.168.1.177 8000作为客户端建立与Linux服务端的TCP连接。
这时执行脚本./netstat_nap.sh,可以看到Windows客户端(端口:64012)和服务端(PID:13035)的TCP通信已经变成ESTABLISHED状态。同时使用命令lsof -i:8000,可以看到进程打开的文件。
4 直接关闭Windows telnet客户端界面并使用Wireshark抓包
在直接关闭telnet界面后,继续使用netstat_nap.sh脚本和lsof命令发现刚才建立的TCP通信出现了CLOSE_WAIT的状态。
在等待2分钟后,在Windows中使用Wireshark抓包发现由于客户端发送了RST+ACK报文给Linux服务端,所以二者的TCP链路已经被复位了:
在Windows中使用Wireshark抓包
这时在Linux中再次使用netstat_nap.sh脚本和lsof命令发现CLOSE_WAIT的状态已经不存在了。
5 关闭Linux telnet客户端
在Windows关闭telnet客户端界面并发送RST+ACK报文后,关闭小节2中在Linux中打开的telnet客户端。这时Linux服务端进程会执行第90行处的close()函数,也即执行正常四次挥手关闭TCP连接。
接着Linux服务端进程继续从内核中已完成连接队列中取出已完成连接,这样之前小节3中Windows telnet建立的客户端连接被读取。如下图所示,服务端进程打印了第80行出的数据(Accept client[192.168.1.110:64012].),但是服务端进程却挂掉了。
CentOS服务端建立监听端口
这时在Linux中再次使用netstat_nap.sh脚本和lsof命令:
6 原因分析
由于Windows客户端的TCP链路在小节4中由于RST的缘故而关闭了,没有读端。那么当Linux服务端执行82行的send()函数时,向之前的socket描述符发送26字节的报文数据时,会收到内核发送过来的SIGPIPE信号,导致服务端进程默认关闭。
因此,如果想捕捉到这个SIGPIPE信号的话,可以将程序17行的SIGNAL_HANDLE宏定义值改成1,那么就会得到如下图所示的情况(进程能正常运行了)。
7 问题延伸
如果在第4小节中关闭Windows客户端界面后,又直接如第5小节所示关闭Linux telnet客户端界面,那么又会出现什么情况呢?于是又重新做了一遍测试,流程同上,下面是测试结果以及分析。
先用netstat和lsof命令查看TCP服务状态,发现监听服务正常:
然后分别用TCPDUMP和Wireshark抓取TCP通信包,截取如下所示。可以发现在Linux telnet客户端完成四次挥手后,服务端进程继续向之前Windows telnet客户端建立的socket描述符发送26字节的报文数据。
因为Windows客户端此时处于FIN_WAIT2状态(Linux服务端处于CLOSE_WAIT状态),所以服务端能继续发其发送数据(即图中的PUSH+ACK报文),接着Windows客户端回应RST+ACK报文,从而两者的TCP链路复位。
在Linux中使用TCPDUMP抓包
在Windows中使用Wireshark抓包
这样Linux服务端进程还是能够正常执行监听任务:
8 其它
网上有人把这种客户端或者服务端异常关闭的连接叫做TCP半关闭(Half-Close),例如网线拔掉、突然断电等,此时对端连接仍认为双方连接处于打开中。