unix网络编程之服务器和客户端上的异常处理讲解
程序员文章站
2022-03-23 09:24:48
1.被中断的系统调用:
//服务端模型:
socket();
bind();
listen();
for(;;)
{
accept();//服务端阻塞在这...
1.被中断的系统调用:
//服务端模型: socket(); bind(); listen(); for(;;) { accept();//服务端阻塞在这里 if(fork()==0) { while() { read(); write(); } } } //客户端模型 socket(); connect(); while() { write(); read(); }
当客户端给服务端发送一个连接请求时,accept解除阻塞,并且fork一个服务器的子进程用来处理本次连接,此时父进程由于死循环,又阻塞在了accept处,当本次连接结束的时候,服务器子进程会给父进程发送一个SIGCHLD信号,此时父进程正在阻塞在了accept处,当父进程收到这个信号并且捕获(wait)后,accept将会返回一个EINTR错误,而父进程不处理这个错误,于是终止。
为了防止这样的情况,我们需要重启被中断的系统调用。
代码改成这样:
for(;;) { if(connfd=accept()<0) { if(errno==EINTR) continue; //重启accept else ... //错误退出 } }
2.同时有很多服务器子进程结束
当同时有很多服务器子进程结束时,父进程会收到多个SIGCHLD信号,并且父进程必须捕获这些信号,避免子进程结束后编程僵死进程,浪费资源。但是在UNIX中,这些信号是不会排队的,所以信号处理函数只执行一次,那么将剩下很多个僵死进程,怎么办呢?
正确的解决方法是调用waitpid而不是wait,并且设置WNOHANG选项,告知waitpid在有尚未终止的子进程运行时,不要阻塞,并且将其放到一个循环中(因为wait函数会阻塞,放在循环中没有什么作用)
void sig_chld(int signo) { pid_t pid; int stat; while((pid=waitpid(-1,&stat,WNOHANG))>0)//这里的waitpid不会阻塞,循环检测结束的进程,并且等所有的进程结束; printf("child %d finished",pid); return; }
3.服务器进程过早终止
客户端首先连接上服务器。并且给服务器发送一句话,此时终止服务器进程。当我们终止服务器进程的时候,服务器会给客户端发送一个FIN,FIN的接收只是告知客户端服务器关闭连接的服务端,并不是代表服务器进程的终止,所以客户端还可以继续发送下一句话。
当服务器收到下一句话的tcp的时候,因为之前的套接字已经关闭,所以服务器会给客户端发送一个RST,但是客户端看不到这个RST,客户端调用write后,立即调用read,返回0,并没有收到EOF,所以会有出错信息:服务器过早终止。
4.SIGPIPE信号
//客户端链接服务器:connect 127.0.0.1 write("hello");//发送给服务器 read("hello"); //从服务器读取回来 //在这里杀死服务器子进程 write("ok?");//发送给服务器,此时上次连接建立的服务器子进程已经被杀死,此时这个socket将会引发一个RST write("hi?");//客户端此时会收到一个SIGPIPE信号,如果没有捕获这个信号(一般设定为忽略),客户端将会崩溃