当客户关闭连接,那么服务器的read函数接收到客户的FIN将导致其返回0。
正常终止客户和服务器的步骤:
(1)当我们键入EOF字符时,fgets返回一个空指针,于是str_cli函数返回。
(2)当str_cli返回到客户的main函数时,main通过调用exit终止。
(3)进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,这就是TCP连接终止序列的前半部分。至此,服务器套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2状态。
(4)当服务器TCP接受FIN时,服务器子进程阻塞于readline调用,于是readline返回0(即服务器TCP递送一个EOF给子进程阻塞中的readline)。这导致str_echo函数返回服务器子进程的main函数。
(5)服务器子进程通过调用exit来终止。
(6)服务器子进程中打开的所有描述符随之关闭。由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个从服务器到客户的FIN和一个从客户到服务器的ACK。至此,连接完全终止,客户套接字进入TIME_WAIT状态。
(7)进程终止处理的另一部分内容是:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。父进程不处理该信号的话,默认行为是忽略,会导致子进程进入僵死状态。(当SIGHLD信号递送时,既然该信号时在父进程阻塞于慢系统调用(accept)时由父进程捕获的,内核就会使accept返回一个EINTR错误(被中断的系统调用),而父进程不处理该错误,于是终止)
信号(signal)就是告知某个进程发生了某个事件的通知,有时也称为软件中断。信号通常是异步发生的,也就是说进程预先不知道信号的准确发生时刻。
信号可以:
1)由一个进程发给另一个进程(或自身);
2)由内核发给某个进程;
例如:SIGCHLD就是内核在任何一个进程终止时发给父进程的一个信号。
僵死进程
设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。如果一个进程终止,而该进程有子进程处于僵死状态,那么它的所有僵死子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵死状态)。
阻塞式系统调用是指因为目前没有必要资源可用而必须等待,知道这些资源变为可用后才能返回。等待期间进程进入睡眠状态。 非阻塞式系统调用是指即使没有必要资源可用也立即返回,不过会告诉调用者发生了这种情况,这样调用者可以继续调用同一个系统调用。