正常启动
tcpserver &
可使用netstat -a来观察,监听套接字的state应为LISTEN,在建立连接后可看到新的连接。
正常终止
客户端发出中断命令,进入了FIN_WAIT_1的状态(发完了FIN),接受到服务器的ACK变为FIN_WAIT_2,接受到服务器的FIN并发送ACK后变为TIME_WAIT,在等待2MSL后正式关闭。
注意:
如果在FIN_WAIT_1状态时收到了FIN(服务器要求关闭),则发送ACK并进入CLOSING状态,收到服务器的ACK进入TIME_WAIT状态。
如果在FIN_WAIT_1状态接受到了FIN和ACK(服务器响应关闭并要求关闭),则发送ACK直接进入TIME_WAIT状态。
服务器收到客户端的FIN后,发送ACK并变为CLOSE_WAIT状态,之后服务器也发送FIN,变为LAST_ACK状态,接受到客户端的ACK后正式关闭。
子进程
在子进程退出后会发送SIGCHLD信号,但是父进程默认是不处理的,子进程则进入了僵死状态,资源没有被回收。如果一个进程挂掉,它的所有子进程都变为僵死,交给它的父进程来处理。处理方法:调用wait函数,可设置一个信号处理函数。当一个子进程终止,会发送该信号给父进程,执行绑定的信号处理函数。如果父进程在被中断时正阻塞于一个系统调用(如accept),那么accept会返回一个EINTER错误,父进程应该处理这个值。(可以设置SA_RESTART标志来自动重启被中断的系统调用)
信号
一旦安装了信号处理函数就一直安装着。
在处理该信号时,该信号是被阻塞的(即不能再次引发该信号),同时可设置sa_mask,在其中指定的信号也会阻塞。
如果信号在被阻塞时产生了多次,但是在解阻塞后只会再引发一次。
信号处理函数
void handler(int signo);
signal
void (*signal(int signo, void (*func)(int))) (int);
typedef void Sigfunc(int);
Sigfunc signal(int signo, Sigfunc *func);
正确处理僵死子进程
如果是在信号处理函数中调用wait,一次只能清理一个子进程,但是可能会有多个子进程同时死亡,这样就无法处理了(因为在信号处理函数运行时,引发多次该信号只会记为一次)。应该在信号处理函数中使用waitpid及WNOHANG选项。
while((pid = waitpid(-1, &stat, WNOHANG)) >0)
// 处理子进程
特殊情况
accept返回前连接终止
也就是在三次握手完成后,客户端却发送了RST。这种情况操作系统不同则有差异,在内核中处理、服务器进程更不看不到,或者返回EPROTO或ECONNABORTED。
服务器进程终止
例如kill掉服务器进程,这样会给客户端发送一个FIN,这样对于客户端来说服务器只是不再发送数据而已,如果客户端调用write,服务器会响应一个RST,如果客户端调用read,会返回EOF。
如果调用两次write
如果说无视RST,仍然写入,会导致内核向进程发送一个SIGPIPE信号,该信号的默认行为是终止进程。
服务器主机崩溃
当客户端调用read会阻塞并最终获得ETIMEOUT或EHOSTUNREACH或ENETUNREACH。
服务器主机崩溃后重启
由于丢失了之前所有的链接信息,服务器会响应RST(客户端收到ECONNRESET)。