1.服务器进程终止(或者服务器进程崩溃,被迫中止)
讨论没有I/O复用的程序的缺陷。若客户端在应对多个描述符(称其为源,都有可能被阻塞在源上的操作),我们不能单纯的阻塞在某个特定源头的输入上,而是应该阻塞在其中任何一个源的输入上(这正是为什么需要I/O复用的原因)。以回射程序举例,客户端程序需要操作两个描述符-套接字和用户输入,若是只单纯阻塞在fgets上等待用户的输入,则不能及时响应套接字状态的变化,一直到主动对套接字执行什么操作的时候才会报错(也就是说,我们杀死服务器子进程时,客户端不会立即被告知已收到FIN)
- 运行此服务程序和客户程序。
- 找到服务器子进程的进程id,并执行kill命令杀死它。(进程终止时,其打开的所有描述符都被关闭(包括套接字描述符),导致向客户发送一个FIN,而客户tcp则响应一个ACK)
- SIGCHLD信号发送给服务器父进程,得到正确处理
- 客户tcp接收到来自服务器tcp的FIN并响应一个ACK,但客户进程此时处于fgets调用的阻塞中,等待从客户端接收到一行文本。(此时客户端并不知道服务器子进程已经终止,只是单纯的接收到了一个FIN)
- 而后,我们在客户上再键入一行文本。str_cli调用writen发送数据给服务子进程,但由于服务子进程已经关闭,于是发送一个RST。我们客户端readline时若先收到第二步中的FIN,则立即返回0,如果先接收到RST,则返回一个ECONNRESET(对方复位连接错误)
2.服务器主机崩溃(或服务器主机突然变得不可达)
- 在客户端上键入一行文本,由writen写入内核。 writen成功返回后,客户随后阻塞于readline调用
- 客户端持续重传数据分节,试图从服务器上接收一个ACK。
- 然而服务器已崩坏,没有任何响应,最终readline调用上返回一个错误。如果是主机崩溃,则返回超时ETIMEOUT,如果是某个中间路由器判定服务器主机不可达,则返回EHOSTUNREACH或ENETUNREACH。(那么问题来了,为什么这里不是writen返回错误,而是readline呢?简单来说,write的返回条件并不是数据成功发送到了服务器,而是数据全部由内核复制到了tcp套接字的发送缓冲区,write就算是成功返回了,而writen就是调用的write 具体参见我的另一篇博文:应用进程写数据到tcp中发生的事情)
3.服务器主机崩溃后重启
- 启动服务器和客户,并在客户键入一行文本以确认连接已建立
- 服务器主机崩溃并重启
- 在客户上键入一行文本,它将作为一个TCP数据分节发送到服务器主机
- 在服务器主机崩溃后重启时,它的tcp丢失了崩溃前的所有连接信息,因此服务器TCP对于所收到的来自客户的数据分节响应一个RST
- 当客户TCP收到该TST时,客户正阻塞于readline调用,导致该调用返回ECONNRESET错误
重点:对客户而言如果想检测服务器主机是否崩溃,即使客户不主动发送数据也要能检测出来,则需要采取其他技术(诸如SO_KEEPALIVE套接字选项或者某些客户/服务器心跳函数),具体详解请参考我的另一篇博文:
4.服务器主机关机
服务器主机关机时发生的事情
- init进程通常先给所有进程发送SIGTERM信号(该信号可被捕获),等待一段固定时间
- 给所有仍然在运行的进程发送SIGKILL信号(不能被捕获)
- 当服务器子进程终止时,所有打开着的描述符都被关闭,发送FIN给客户端TCP
- 。。接下来的过程略