如何屏蔽SIGPIPE消息


最近被SIGPIPE消息坑了很久.所以立志要一次性解决它.

SIGPIPE消息的由来

对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.

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

如何屏蔽SIGPIPE消息_第1张图片
示意图

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

那么,我们如何屏蔽SIGPIPE消息?

单进程的SIGPIPE消息屏蔽方法

对于单进程而言,有下面几种方法可以尝试一下:

使用signal函数

有意思的是,这种方法在我的电脑上完全行不通,我不知道是不是都是这样,但是还是在这里贴一下:

signal(SIGPIPE, SIG_IGN); /* 忽略掉SIGPIPE消息 */

signal函数是很老的东西,它由ISO C定义,由于ISO C不涉及多线程,进程组等,所以它对信号的定义非常模糊,以致于对Unix系统而言几乎毫无用处.所以说,不推荐系统提供的signal函数.

如果你实在要使用signal的话,stevents老爷子用sigaction.函数给我们重新实现了一遍signal函数,我们比较推荐这个版本.这里也顺带在这里贴一下:

void unix_error(const char *msg) /* unix-style error */
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(0);
}

typedef void handler_t(int);

handler_t *Signal(int signum, handler_t *handler) /* 用于注册信号处理函数 */
{
    struct sigaction action, old_action;

    action.sa_handler = handler; /* 信号处理函数 */
    sigemptyset(&action.sa_mask); /* block sigs of type being handled */
    action.sa_flags = SA_RESTART; /* 如果可能的话,重启系统调用  */

    if (sigaction(signum, &action, &old_action) < 0)
        unix_error("Signal error");
    return (old_action.sa_handler);
}

我测试一下这个函数,对其他的信号貌似都很管用,在我的电脑上对SIGPIPE消息却没什么用处,真是奇怪.

信号集

另外的方法也是有的,除了signal方法,其实我们也可以使用信号集的方法.
我这里给一个函数:

void BlockSigno(int signo) { /* 阻塞掉某个信号 */
    sigset_t signal_mask;
    sigemptyset(&signal_mask); /* 初始化信号集,并清除signal_mask中的所有信号 */
    sigaddset(&signal_mask, signo); /* 将signo添加到信号集中 */
    sigprocmask(SIG_BLOCK, &signal_mask, NULL); /* 这个进程屏蔽掉signo信号 */
}

然后调用:

BlockSigno(SIGPIPE);

即可.如果想具体了解这几个函数,可以去看APUE.

多线程的SIGPIPE屏蔽方法

其实线程的屏蔽方法和单进程差不太多,不行,你可以看:

void BlockSigno(int signo)
{
    sigset_t signal_mask;
    sigemptyset(&signal_mask);
    sigaddset(&signal_mask, signo);
    pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
}

然后我们调用:

BlockSigno(SIGPIPE);

即可.当然,上面的代码都做了简化,没有错误处理,你自己可以添加.

你可能感兴趣的:(如何屏蔽SIGPIPE消息)