@lishuhuakai
2016-11-11T20:32:22.000000Z
字数 1895
阅读 1927
最近被SIGPIPE
消息坑了很久.所以立志要一次性解决它.
SIGPIPE
消息的由来对一个对端已经关闭的socket
调用两次write
, 第二次将会生成SIGPIPE
信号, 该信号默认结束进程.
具体的分析可以结合TCP
的”四次握手”关闭. TCP
是全双工的信道,可以看作两条单工信道, TCP
连接两端的两个端点各负责一条. 当对端调用close
时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN
包. 按照TCP
协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP
协议的限制, 一个端点无法获知对端已经完全关闭.
对一个已经收到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);
即可.当然,上面的代码都做了简化,没有错误处理,你自己可以添加.