tcp服务器端如何主动关闭连接(tcp关闭连接的状态)

正常启动

我们一起来学习使用netstat调试网络程序。

启动服务器程序:./tcpserv01 & (&表示后台启动)

服务器程序启动后,调用socket bind listen 和 accept,并阻塞于accept 调用。运行netstat程序来检查服务器监听套接字的状态,如下:LISTEN。有没有注意到-a选项可以查看所有监听套接字。我们的监听端口是9877,监听地址是通配地址0。

启动客户程序:./tcpcli01 我修改了代码中IP地址为环回地址(127.0.0.1)

客户端调用socket 和 connect,引起TCP三次握手,这是服务器的accept 开始忙活了,当三次握手完成,客户端的connect 和服务端的accept 均返回,连接建立。

客户端:调用str_cli函数,该函数阻塞于fgets 调用。服务端:调用fork,由子进程调用str_echo函数,该函数阻塞于read 调用。服务端:父进程再次阻塞于accept 函数并阻塞,等待下一个客户连接。

提示:客户端connect 在接收到三次握手的第二个握手报文返回,服务端accept 在接收到三次握手的第三个握手报文返回。

可以看出我们的子进程和客户端建立了TCP连接,服务端端口是9877,客户端端口是42120。

通过ps 来看看进程间的关系:ps -t pts/0 -o pid,ppid,tty,stat,args,wchan

提示:我在这里重启了客户端和服务端的程序,所以进程ID、端口可能会发生变化。

pts/0:0号伪终端,服务端跑的终端pts/1:1号伪终端,客户端跑的终端

注意:STAT列的S表示进程在为等待某些资源而睡眠,有+号表示光标在该进程,可以输入数据。 当进程阻塞于accept或connect时,输出inet_csk_accept。当进程阻塞于套接字输入或输出时,输出sk_wait_data。当进程阻塞于终端IO时,输出wait_woken。

正常关闭

提示:在分析正常关闭网络程序的时候,我们再次重启了程序,而这是不必要的,如果你时间充裕,能一次性把这些东西都研究懂,当下是正常启动后。

现在们试图关闭客户端连接,我们输入EOF(Control+D)结束客户端连接,再看看连接状态,发现刚刚连接着的服务端没了,客户端状态变成TIME_WAIT,监听服务器不变,依然在等待。

当输入EOF字符,fgets 返回一个空指针,于是str_cli 返回。str_cli 返回到客户端的main 函数时,调用exit 结束进程。结束进程会做很多事,其中包括关闭所有打开的描述符。所以,客户打开的套接字描述符由内核关闭,这将触发客户TCP发一个FIN报文给服务端,服务端收到FIN报文将回送一个ACK报文。此时,服务器套接字处于CLOSE_WAIT状态,客户套接字处于FIN_WAIT_2状态。当服务端TCP上层接收到FIN报文时,处于阻塞状态的readline 函数返回0,str_echo返回。str_echo 返回到服务端的main 函数时,调用exit 结束进程。服务端子进程中打开的所有描述符随即关闭,关闭服务端套接字将会触发TCP四次挥手的最后2个阶段,服务端内核将发送一个FIN报文给客户端,客户端收到FIN报文,发送一个ACK报文。至此,连接完全终止,客户套接字进入TIME_WAIT 状态。(上图)服务端还需要注意一点,进程结束还需要做一件事:服务端子进程结束时,给父进程发送一个SIGCHLD 信号,由于我们没有在代码里捕获这个信号,并且这个信号的默认行为是被忽略。所以,就是下图我们看到的,父进程没有处理,子进程变成僵尸进程。

我们在运行一个客户端,然后发送EOF 字符,再次观察,如下图所示,我们发现又多了一个僵尸进程。(Z 表示僵尸进程)

接下来,咱们一起学习一下如何徒手打僵尸,很简单擒贼先擒王,手动kill 掉僵尸的爸爸。

参考文献:《UNIX网络编程 卷1:套接字联网API》