关于网络编程时候SIGPIPE信号

来源:互联网 发布:多源数据 编辑:程序博客网 时间:2024/06/06 16:29

    在linux下写socket的程序的时候,如果尝试 send 到一个 disconnected 的 socket上,(读取端已经关闭)就会让底层抛出一个SIGPIPE信号。该信号默认情况下会终止当前进程,大多数时候这都不是我们期望的。


首先用代码测试正常的 server client 端TCP通信:
http://blog.csdn.net/xsckernel/article/details/8543352


然后在服务器端  accept 成功之后调用close(fd)模仿这种情形。

        while(1)        {                if((connect_fd = accept(listen_fd,(struct sockaddr *)&peer_addr,&addr_len)) < 0)                        continue;                pid = fork();                if(pid == 0){                        close(listen_fd);                        close(connect_fd);                        printf("peer ip : %s\n",inet_ntoa(peer_addr.sin_addr));                        printf("peer port : %d\n",ntohs(peer_addr.sin_port));                        /*read(connect_fd,buf,sizeof(buf));                        write(connect_fd,buf,strlen(buf) + 1);*/                }else{                         close(connect_fd);                }        }

再次测试 如果再在客户端上连续输入两次,客户端就会退出。


为了验证客户端是接收到 SIGPIPE 信号而退出的,因此在客户端的代码里添加下面一个函数:

void sig_pipe(int sign){        printf("Catch a SIGPIPE signal\n");}

然后在主函数里添加  signal(SIGPIPE, sig_pipe); 函数,
并加上头文件:
#include <signal.h>


接着做上面的实验,那么会在客户端一边看到输出:

shichao@ubuntu:~/Downloads$ ./client 127.0.0.1 8888>aaarecv : >bbbCatch a SIGPIPE signalrecv : >Love youCatch a SIGPIPE signalrecv : >GirlCatch a SIGPIPE signalrecv : 


现在来考察一下,为什么上面的输出中需要连续两次才会导致客户端退出。可以用 wireshark 这样的嗅探器进行相关数据分析。


     具体的分析可以结合TCP的"四次握手"关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown.

   对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.



因此我们需要重载这个信号的处理方法。调用以下代码,即可安全的屏蔽SIGPIPE:

    struct sigaction sa;    sa.sa_handler = SIG_IGN;    sigaction( SIGPIPE, &sa, 0 );


注意对于上面测试用到的客户端代码因为两次发送都是用户输入的,时间间隔比较长。 但如果是由程序自动发送那么有时候捕捉不到SIGPIPE信号。这是因为第二次调用send函数发送数据时有可能服务器端的RST还未返回。   在第二次发送数据前sleep一下程序,使得RST能在send前返回。再次测试程序,这样就都能捕捉到SIPPIPE信号了。





原创粉丝点击