欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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信号,如果没有捕获这个信号(一般设定为忽略),客户端将会崩溃