菜鸟笔记
提升您的技术认知

《unix网络编程》tcp服务器的几种常见状况分析

accept返回前连接终止

《unix网络编程》(10)wait/waitpid处理僵死进程(SIGCHLD信号)该文章中介绍了客户正常终止时,由于父进程使用wait处理SIGCHLD信号时,阻塞于accept,内核会使得accept返回一个EINTR错误(被中断的系统调用)。

         另一种情形也会导致accept返回非致命的错误:三次握手完成后,客户TCP发送一个RST(复位);此时服务器端即将调用accept。

服务器进程终止

启动客户和服务器,客户端发送”test1“正常回显,然后杀死服务器子进程,模拟服务器进程崩溃。其过程如下:

(1)kill子进程,子进程所有描述符关闭,导致向客户发FIN,客户响应一个ACK。这是TCP连接终止的前部分。

(2)SIGCHLD信号发到父进程,被正确处理。

(3)客户没其他问题,但是客户阻塞于fgets调用上,等待从终端输入。

(4)此时,在客户端键入”test2 after kill”:客户TCP把数据发送到服务器,TCP允许这样做,因为客户收到FIN指标是服务器进程关闭了连接的服务器端,从而不再发数据(半连接)。FIN的接收没有告知客户TCP服务器进程终止。服务器收到数据,由于之前的连接已经终止,所以响应一个RST

(5)然而客户看不到RST,因为它调用write后立即调用read,而read的是第2步中的服务器的FIN,所以read立即返回0。客户没想到会收到EOF,所以以出错信息退出(服务器过早终止)。

        该例子的问题在于:FIN到达套接字,客户阻塞于fgets。客户实际上对应两个描述符——套接字和用户输入,它不能如我们编写的客户端那样阻塞于这两个中的一个,而应该阻塞于其中任何一个输入上。这正是select和poll的目的之一。

SIGPIPE信号

         如上边小节中,往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据。但是在收到RST后继续写操作,内核会向进程发送SIGPIPE信号;其默认行为是终止进程。对于这个信号的处理我们通常忽略即可。无论进程捕获该信号并从其处理函数返回,还是忽略,写操作都会返回EPIPE错误。

服务器主机崩溃

必须在不同主机运行服务器和客户机才能模拟该情形。

启动服务器、客户机;从网络中断开服务器。此时过程:

(1)服务器崩溃后,不会发送任何东西。

(2)客户键入文本,由write写入内核,再由客户TCP发送。客户阻塞于read,等待回射。

(3)客户TCP持续重传数据试图收到服务器的ACK。一般会等待数分钟才放弃重传。客户最终放弃(假设此段时间内服务器没恢复)。既然客户阻塞于read,所有返回错误ETIMEOUT。然而如果某个中间路由判定服务器不可达,会响应一个目的不可达ICMP消息,那么返回的错误时EHOSTUNREACH或ENETUNREACH。

         尽管最终客户最终知道服务器不可达或崩溃,但已经过了数分钟。所用方法是对read设置一个超时

        上述的情形要给服务器发数据才知道服务器崩溃。如果不主动发数据还想检测服务器主机崩溃,就要采用SO_KEEPALIVE套接字选项

服务器崩溃后重启

服务器崩溃后重启,其TCP丢失了崩溃前的所有连接信息,因此对所受到的来自客户的数据响应一个RST;客户收到RST时正阻塞于read,所以会返回ECONNRESET错误。

客户要检测服务器崩溃与否,又不主动发数据,就需要采用SO_KEEPALIVE套接字选项或某些客户/服务器心搏函数

服务器主机关机

Unix系统关机,init进程向所有进程发送SIGTERM信号(可被捕获),等待5-20秒,然后给还在运行进程发SIGKILL信号(不能捕获)。

如果不捕获SIGTERM,那么服务器将由SIGKILL终止。当服务器子进程终止时,和上边“服务器进程终止”一节中一样,正如那节中所说,要在客户端使用select后poll使得服务器一经终止,客户就能检测到