SIGPIPE信号
当往一个写端关闭的管道或socket连接中连续写入数据时会引发SIGPIPE信号。在TCP通信中,当通信的双方中的一方close一个连接时,若另一方接着发数据,根据TCP协议的规定,会收到一个RST响应报文,若再往这个已关闭的连接发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不能再写入数据。
图片来自(https://blog.csdn.net/u010821666/article/details/81841755)
处理:
因为SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为写操作的错误而导致程序退出,尤其是作为服务器程序来说就更恶劣了。所以我们应该对这种信号加以处理,在程序中可以捕捉该信号,然后执行相应的处理程序。在这里,介绍三种处理SIGPIPE信号的方式:
一、给SIGPIPE设置SIG_IGN信号处理函数,忽略该信号
// signum: 信号类型
// handler: 信号处理函数,是个函数指针
// 这个处理信号函数带有一个int型参数,并应返回void
void (*signal(int signum,void(* handler)(int)))(int);
// 例如
signal(SIGPIPE, SIG_IGN); // SIG_IGN 忽略信号
引发SIGPIPE信号的写操作将设置errno为EPIPE。所以,第二次往关闭的socket中写入数据时, 会返回-1, 同时errno置为EPIPE. 这样,通过特殊的判断便能知道对端已经关闭,然后进行相应处理,而不会导致整个进程退出.
另一种设置信号处理函数的方法(该方法更稳健,常用)
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
signum参数指出要捕获的信号类型,act参数指定新的信号处理方式,oldact参数输出先前信号的处理方式(如果不为NULL的话)
使用该函数需要先理解struct sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
- sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
- sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
- sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
- SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
- SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
- SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记,那么在该信号处理函数运行时,内核将不会阻塞该信号sigaction
sigaction 实现忽略SIGPIPE信号
struct sigaction sa;
/* 设置信号忽略 */
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = SIG_IGN; //这个地方也可以是其他处理函数(自定义也可)
// 如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
sa.sa_flags |= SA_RESTART;
// sigfillset()用来将参数set信号集初始化,
// 把所有的信号加入到此信号集里
// 然后所有的信号标志位置为1,屏蔽所有的信号
sigfillset(&sa.sa_mask);
sigaction(SIGPIPE,&sa,NULL);
二、使用send函数的MSG_NOSIGNAL 标志来禁止写操作触发SIGPIPE信号。
send(sockfd , buf , size , MSG_NOSIGNAL);
我们可以根据send函数反馈的errno来判断socket的读端是否已经关闭;
三、EPOLLRDHUP
我们也可以通过IO复用函数来检测管道和socket连接的读端是否已经关闭。以EPOLL为例,当socket连接被对方关闭时,socket上的EPOLLRDHUP事件将被触发。
epoll_event event;
event.events = EPOLLIN | EPOLLRDHUP;
SIGALRM信号
他的作用是设置进程隔多久后会收到一个SIGALRM信号。通过alarm系统调用设置触发时间。(触发一次之后需要重新设置时间)
void handle_alarm()
{
exit(0);
}
int main(int argc, char *argv[])
{
signal(SIGALRM, handle_alarm);
alarm(10); // 10S触发SIGALRM信号,然后执行信号处理函数
while(1) {}
}
SIGINT && SIGTERM && SIGKILL
SIGINT(ctrl+c):信号被当前进程树接收到,也就是说,不仅当前进程会收到信号,它的子进程也会收到
SIGTERM(kill {pid} ):只有当前进程收到信号,子进程不会收到。如果当前进程被kill了,那么它的子进程的父进程将会是init,也就是pid为1的进程
SIGKILL(kill -9 {pid}):与SIGTERM 不同的是,此信号不会被阻塞