服务器编程中通常有三大类事件需要处理,一类是I/O事件,一类是信号事件,一类是时间事件。
信号事件是一种异步事件,当信号来临时,主逻辑会被打断去执行信号处理函数。而信号到来的时机是不确定,如果此时信号处理函数会去访问一个已经被锁住的资源,那么这个线程就会被阻塞。所以信号处理函数应该是可重入的。
一般信号处理时会将一些信号屏蔽,为了不屏蔽这些信号太久,同时也不至于主逻辑被冲散,一种解决方案是:信号处理函数只是简单的通知主循环(用于处理I/O事件)并告诉信号值,真正的信号处理逻辑被主循环调用,根据信号值做出相应的处理。信号处理函数和主循环之间通常用管道做通信。信号处理函数从管道的写端写入信号值,主循环从管道的读端读取信号值。因为主循环本身就要利用I/O复用函数监听链接进来的socket,所以将这个管道一并注册进I/O复用函数就能在主循环中及时得到信号到来的通知。
int pipefd[2];
...
void sig_handler(int sig)
{
int save_errno = errno;
int msg = sig;
send(pipefd[1], (char*)&msg, 1, 0);//将信号按字节写入管道,以通知主循环
errno = save_errno;
}
void addsig(int sig)
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART; //信号如果打断了慢速系统调用,中断处理完成之后继续恢复系统调用
sigfillset(&sa.sa_mask);//在信号处理函数中屏蔽所有信号
assert(sigaction(sig, &sa, NULL) != -1);
}
void handle_sig(int sig)
{
switch(sig)
{
case SIGCHLD:
...
case SIGHUP:
...
case SIGTERM:
...
case SIGINT:
...
...
}
}
...
int main(int argc, char **argv)
{
...
ret=socketpair(PF_UNIX,SOCK_STREAM,0,pipefd);
setnonblocking(pipefd[1]);
addfd(epollfd,pipefd[0]);
while(true)
{
int ret = epoll_wait(epollfd, events, MAX_EVENTS, -1);
for (int i = 0; i < ret; i++)
{
if(events[i].fd == pipefd[0] && events[i].events & EPOLLIN) //接收到信号
{
char signals[1024];
int num = recv(pipefd[0], signals, sizeof(signals), 0);
if (num == -1)
continue;
else if (num == 0)
continue;
else
{
//每个信号值占1字节,所以按字节来逐个接收信号
//可能处理的时候收到了多个信号
for (int i = 0; i < num; i++)
{
handle_sig(signals[i]);
}
}
}
}
}
...
}
参考资料
《Linux高性能服务器编程》
http://www.2cto.com/os/201109/104474.html
http://blog.csdn.net/jasonliuvip/article/details/23305297