• 网络编程中的 SIGPIPE 信号


    处理 SIGPIPE

      在网络编程中经常会遇到SIGPIPE信号,默认情况下这个信号会终止整个进程,当然你并不想让进程被SIGPIPE信号杀死。我们不禁会这样思考:

    • 在什么场景下会产生SIGPIPE信号?
    • 要怎样处理SIGPIPE信号?

      SIGPIPE产生的原因是这样的:如果一个 socket 在接收到了 RST packet 之后,程序仍然向这个 socket 写入数据,那么就会产生SIGPIPE信号。
      这种现象是很常见的,譬如说,当 client 连接到 server 之后,这时候 server 准备向 client 发送多条消息,但在发送消息之前,client 进程意外奔溃了,那么接下来 server 在发送多条消息的过程中,就会出现SIGPIPE信号。下面我们看看 server 的代码:

    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    #define MAXLINE 1024
    void handle_client(int fd)
    {
        // 假设此时 client 奔溃, 那么 server 将接收到 client 发送的 FIN
        sleep(5);
        // 写入第一条消息
        char msg1[MAXLINE] = {"first message"}; 
        ssize_t n = write(fd, msg1, strlen(msg1));
        printf("write %ld bytes
    ", n);
        // 此时第一条消息发送成功,server 接收到 client 发送的 RST
        sleep(1); 
        // 写入第二条消息,出现 SIGPIPE 信号,导致 server 被杀死
        char msg2[MAXLINE] = {"second message"};
        n = write(fd, msg2, strlen(msg2));
        printf("%ld, %s
    ", n, strerror(errno));
    }
    int main()
    {
        unsigned short port = 8888;
        struct sockaddr_in server_addr;
        bzero(&server_addr, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(port);
        int listenfd = socket(AF_INET , SOCK_STREAM , 0);
        bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
        listen(listenfd, 128);
        int fd = accept(listenfd, NULL, NULL);
        handle_client(fd);
        return 0;
    }
    root@ubuntu:~/c++# ./pipe 
    write 13 bytes
    root@ubuntu:~/c++# 

    客户端

    deamon启动

    $ gcc -o server server.c 
    $ ./server &              # 后台运行 server
    $ nc localhost 8888       # 运行 nc 连接到 server
    ^C                        # Ctrl-C 杀死 nc
    write 13 bytes
    [1]+  Broken pipe             ./server

    让我们分析一下整个过程:

    • client 连接到 server 之后,client 进程意外奔溃,这时它会发送一个 FIN 给 server。
    • 此时 server 并不知道 client 已经奔溃了,所以它会发送第一条消息给 client。但 client 已经退出了,所以 client 的 TCP 协议栈会发送一个 RST 给 server。
    • server 在接收到 RST 之后,继续写入第二条消息。往一个已经收到 RST 的 socket 继续写入数据,将导致SIGPIPE信号,从而杀死 server。

    对 server 来说,为了不被SIGPIPE信号杀死,那就需要忽略SIGPIPE信号:

    int main()
    {
        signal(SIGPIPE, SIG_IGN);  // 忽略 SIGPIPE 信号
        // ...
    }

    重新运行上面的程序,server 在发送第二条消息的时候,write()会返回-1,并且此时errno的值为EPIPE,所以这时并不会产生SIGPIPE信号:

    char msg2[MAXLINE] = {"second message"};
    n = write(fd, msg2, strlen(msg2));
    printf("%ld, %s ", n, strerror(errno)); // -1, Broken pipe
     
     
     

    demo2

    server
    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    #define SERV_PORT 8000
     
    int main(int argc, const char * argv[])
    {
     
        struct sockaddr_in serverAdd;
        struct sockaddr_in clientAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
        serverAdd.sin_port = htons(SERV_PORT);
        
        socklen_t clientAddrLen;
        
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        int yes = 1;
        setsockopt(listenfd,
                   SOL_SOCKET, SO_REUSEADDR,
                   (void *)&yes, sizeof(yes));
        
        if (listenfd < 0) {
            printf("创建socket失败
    ");
            return -1;
        }
        
        int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (bindResult < 0) {
            printf("绑定端口失败
    ");
            close(listenfd);
            return -1;
        }
        
        listen(listenfd, 20);
        
        int connfd;
        unsigned char recvMsg[246988];
        unsigned long long totalSize = 0;
        
        clientAddrLen = sizeof(clientAdd);
        connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
        if (connfd < 0) {
            print#include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    #define SERV_PORT 8000
     
    int main(int argc, const char * argv[])
    {
     
        struct sockaddr_in serverAdd;
        struct sockaddr_in clientAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
        serverAdd.sin_port = htons(SERV_PORT);
        
        socklen_t clientAddrLen;
        
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        int yes = 1;
        setsockopt(listenfd,
                   SOL_SOCKET, SO_REUSEADDR,
                   (void *)&yes, sizeof(yes));
        
        if (listenfd < 0) {
            printf("创建socket失败
    ");
            return -1;
        }
        
        int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (bindResult < 0) {
            printf("绑定端口失败
    ");
            close(listenfd);
            return -1;
        }
        
        listen(listenfd, 20);
        
        int connfd;
        unsigned char recvMsg[246988];
        unsigned long long totalSize = 0;
        
        clientAddrLen = sizeof(clientAdd);
        connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
        if (connfd < 0) {
            printf("连接失败
    ");
            return -1;
        }
        else{
    //        这里我们用于测试,只接收一个连接
            close(listenfd);
        }
        
        close(connfd);
        
        return 0;
    }f("连接失败
    ");
            return -1;
        }
        else{
    //        这里我们用于测试,只接收一个连接
            close(listenfd);
        }
        
        close(connfd);
        
        return 0;
    }

    client

    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    
    #define SERV_PORT 8000
    #define SERV_ADDR "10.10.16.82"
    int main(int argc, const char * argv[])
    {
     
        struct sockaddr_in serverAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
        serverAdd.sin_port = htons(SERV_PORT);
        
        int connfd = socket(AF_INET, SOCK_STREAM, 0);
        
        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (connResult < 0) {
            printf("连接失败
    ");
            close(connfd);
            return 0;
        }
        
        ssize_t writeLen;
        char sendMsg[5000] = {0};
        unsigned long long totalSize = 0;
        
        while (1) {
            
            writeLen = write(connfd, sendMsg, sizeof(sendMsg));
            if (writeLen < 0) {
                printf("发送失败 errno =  %s
    ",strerror(errno));
                return 0;
            }
            else
            {
                totalSize += writeLen;
                printf("发送成功 totalSize = %ld
    ",totalSize);
            }       
            
        } 
        
        close(connfd);
        
        return 0;
    }
    [root@bogon ~]# ./client
    发送成功 totalSize = 5000
    发送成功 totalSize = 10000
    发送成功 totalSize = 15000
    发送成功 totalSize = 20000
    发送成功 totalSize = 25000
    发送失败 errno =  Connection reset by peer
    可以看到客户端发送第130001-135000个字节的时候程序在write方法处崩溃,是因为TCP套接字发送缓冲区的大小为131768字节,在发送前130000个字节的时候发送缓冲区还未满,因此write方法返回成功,接着继续发送
    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client,此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。
     

    当一个进程向某个已收到RST的套接字执行写操作时,(此时写操作返回EPIPE错误)内核向该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止;

    继续修改客户端程序如下,服务端不变:

    [root@bogon ~]# ./client
    发送成功 totalSize = 5000
    读取失败 errno = 104 and  Connection reset by peer 
    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    
    #define SERV_PORT 8000
    #define SERV_ADDR "10.10.16.82"
    int main(int argc, const char * argv[])
    {
     
        struct sockaddr_in serverAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
        serverAdd.sin_port = htons(SERV_PORT);
        
        int connfd = socket(AF_INET, SOCK_STREAM, 0);
        
        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (connResult < 0) {
            printf("连接失败
    ");
            close(connfd);
            return 0;
        }
        
        ssize_t writeLen;
        char recvMsg[65535];
            ssize_t readLen;
        char sendMsg[5000] = {0};
        unsigned long long totalSize = 0;
        
        while (1) {
            
            writeLen = write(connfd, sendMsg, sizeof(sendMsg));
            if (writeLen < 0) {
                printf("发送失败 errno =  %s
    ",strerror(errno));
                return 0;
            }
            else
            {
                totalSize += writeLen;
                printf("发送成功 totalSize = %ld
    ",totalSize);
            }       
                    sleep(1);
                    readLen = read(connfd, recvMsg, sizeof(recvMsg));
                if (readLen < 0) {
                        printf("读取失败 errno = %d and  %s 
    ",errno, strerror(errno));
                        return;
                 }
                 else
                 {
                        printf("readLen:%ld
    ",readLen);
                  }
            
        } 
        
        close(connfd);
        
        return 0;
    }

    客户端向服务端写5000字节以后先休眠一秒是为了将数据发送出去,确认TCP协议层已收到服务端响应的RST分节,然后再进行读操作,此时read返回-1.而不再是0;

    先运行服务端,再运行客户端,客户端打印信息如下:

    发送成功 totalSize = 5000
    读取失败 errno = 54

    #defineECONNRESET 54/* Connection reset by peer */

    当一个进程向某个已收到RST的套接字执行读操作时,(此时读操作返回ECONNRESET错误)

    抓包信息如下:

    上述情况会引发一个问题:服务器主机进程终止或者崩溃后重启,客户端在不write的情况下不会知道,read会返回ECONNRESET错误或者超时;

    解决方法用select:

    1、如果对端TCP发送一个FIN(对端进程终止),那么该套接字变为可读,并且read返回0;

    2、如果对端TCP发送一个RST(对端主机崩溃并重新启动),那么该套接字变为可读,并且read返回-1,而errno中含有确切的错误码;

    非阻塞模型下,select或者epoll会返回sockfd可读,应用层对其进行读取时,read()会报错RST。 

    情况三:

    修改客户端程序如下,服务端不变;

    运行服务端,再运行客户端程序,客户端打印连接成功,if语句开头会休眠20秒,(服务端程序里面,接收一个连接以后就close套接字然后立马退出程序了)在这期间内再次打开服务端,等待客户端的读取数据的分节到达,然后返回一个RST分节给客户端,是因为TCP接收到一个根本不存在的连接上的分节;服务器主机崩溃后重启:它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所有收到的来自客户的数据分节响应一个RST;

    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <errno.h>
    #include <signal.h>
    
    #define SERV_PORT 8000
    #define SERV_ADDR "10.10.16.82"
    int main(int argc, const char * argv[])
    {
     
        struct sockaddr_in serverAdd;
        
        bzero(&serverAdd, sizeof(serverAdd));
        serverAdd.sin_family = AF_INET;
        serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
        serverAdd.sin_port = htons(SERV_PORT);
        
        int connfd = socket(AF_INET, SOCK_STREAM, 0);
        
        int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
        if (connResult < 0) {
            printf("连接失败
    ");
            close(connfd);
            return 0;
        }
        
        ssize_t writeLen;
        char recvMsg[65535];
            ssize_t readLen;
        char sendMsg[5000] = {0};
        unsigned long long totalSize = 0;
        
        while (1) {
            
                    sleep(10);
                    printf("sleep over");
            writeLen = write(connfd, sendMsg, sizeof(sendMsg));
            if (writeLen < 0) {
                printf("发送失败 errno =  %s
    ",strerror(errno));
                return 0;
            }
            else
            {
                totalSize += writeLen;
                printf("发送成功 totalSize = %ld
    ",totalSize);
            }       
                    sleep(2);
                    readLen = read(connfd, recvMsg, sizeof(recvMsg));
                if (readLen < 0) {
                        printf("读取失败 errno = %d and  %s 
    ",errno, strerror(errno));
                        return;
                 }
                 else
                 {
                        printf("readLen:%ld
    ",readLen);
                  }
            
        } 
        
        close(connfd);
        
        return 0;
    }

  • 相关阅读:
    AutoMapper bool与string互转
    Self-signed SSL certificates are being blocked
    Visual Studio 2019设置自定义头部注释
    用多进程同步方法演示“桔子-苹果”问题
    实验教学管理系统 c语言程序代写源码下载
    模拟游客一天的生活与旅游java程序代写源码
    Java作业代写
    快餐店运行模拟C++程序源码代写
    hadoop-2.0.0-cdh4.2.1安装手册
    CDH4 HA方案
  • 原文地址:https://www.cnblogs.com/dream397/p/14686608.html
Copyright © 2020-2023  润新知