最基本的信号处理函数signal
函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum
参数:传入的信号类型handler
参数:处理信号的函数,函数接受一个整型参数,用于表示信号;函数的返回值是void
类型。在函数内部编写处理信号的方法。
代码实例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int signo) {
if (signo == SIGINT) {
puts("received SIGINT");
}
}
int main() {
if (signal(SIGINT, sig_handler) == SIG_ERR) {
puts("can not catch SIGINT");
}
while (1) {
sleep(1);
}
return 0;
}
sigaction函数
signal
已经不推荐使用,现在更加推荐的是该函数。
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
signum
:信号的类型。act
:安装信号,并接受到信号后,采取的行动。oldact
:
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
是用于处理信号的函数,sa_sigaction
也是类似的函数;在有些架构中,这两者使用了union
结构,因此最好不要同时使用;一般使用sa_handler
函数即可。sa_restorer
可以暂时不用管它的用处。sg_mask
是掩码,用于声明需要屏蔽的信号集。sa_flags
是一个信号的标志集合,指明了处理信号的行为,具体参照手册。
代码实例:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void sig_handler(int sig) {
if (sig == SIGINT) {
puts("received SIGINT");
}
}
int main() {
struct sigaction ac;
memset(&ac, 0, sizeof(ac));
ac.sa_handler = sig_handler;
if (sigaction(SIGINT, &ac, NULL) < 0) {
perror("sigaction() error\n");
}
while (1) {
sleep(1);
}
return 0;
}
使用信号集
信号集函数类似于select
的fd_set
,是一组信号,本质上是一个长整型数组,元素的每一位表示一个信号,通过使用系统内置的一组函数来进行操作。
#include <signal.h>
int sigemptyset(sigset_t* _set); // 清空信号集
int sigfillset(sigset_t* _set); // 信号集中设置所有的信号
int sigaddset(sigset_t* _set, int _signo); // 信号_signo添加到信号集
int sigdelset(sigset_t* _set, int _signo); // 测试信号是否在_set中
// 设置或者查看进程的掩码,操作类型由how决定,_set是新的掩码,_oset是之前的掩码
int sigprocmask(int _how, const sigset_t* _set, int _oset);
以下内容来自Linux高性能服务器编程
信号处理函数和程序的主循环不是一个路线,在程序设计的时候,最好是尽快处理信号,避免出现信号被忽略的情况。因为信号在处理期间,系统不会再次触发它。我们一般把信号处理的逻辑放到主循环中,处理信号函数被触发的时候,只是通知主循环接收信号,并传递相应的信号值。一般使用管道机制把信号传递给主循环,处理函数在管道的入口写信号,主循环在管道的出口读信号,之后调用I/O复用函数来监听文件描述符即可。
补充说明:Linux的每个占用一个字节,等价一个char
类型长度,即使我们传入参数是整型,但是在处理的时候,还是要按照字节进行计算。
统一事件源代码实例。
#include <stdio.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#define MAX_EVENT_NUMBER 1024
#define MAX_WAIT_NUMBER 32
#define MAX_SIGNAL_NUMBE 1024
static int pipefd[2];
// fd设置为非阻塞模式
int setnonblocking(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
assert(fcntl(fd, F_SETFL, new_option) != -1);
return old_option;
}
// epoll注册事件
void addfd(int epollfd, int fd) {
epoll_event event;
bzero(&event, sizeof(event));
event.data.fd = fd;
event.events = EPOLLET | EPOLLIN;
assert(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) != -1);
}
// 信号处理函数,把信号发给主循环
void sig_handler(int sig) {
int save_errno = errno;
int msg = sig;
assert(send(pipefd[1], (char*)&msg, sizeof(char), -1) != -1);
errno = save_errno;
}
void addsig(int sig) {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
assert(sigaction(sig, &sa, NULL) != -1);
}
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <port of server>\n", argv[0]);
return 1;
}
int port = atoi(argv[1]);
if (port < 1024 || port > 65535) {
perror("port error\n");
return 1;
}
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(port);
address.sin_family = SOCK_STREAM;
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("sock() error\n");
return 1;
}
setnonblocking(listenfd); // 非阻塞模式的监听
int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
if (ret < 0) {
perror("bind() error\n");
close(listenfd);
return 1;
}
ret = listen(listenfd, MAX_WAIT_NUMBER);
if (ret < 0) {
perror("listen() error\n");
close(listenfd);
return 1;
}
int epollfd = epoll_create1(0);
if (epollfd < 0) {
perror("epoll_create1() error\n");
close(listenfd);
return 1;
}
epoll_event events[MAX_EVENT_NUMBER];
bzero(events, sizeof(events));
addfd(epollfd, listenfd);
// 这里添加感兴趣的信号
addsig(SIGINT);
addsig(SIGCHLD);
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);
if (ret < 0) {
perror("socketpair() error\n");
close(listenfd);
close(epollfd);
return 1;
}
bool stop_server = false;
while (!stop_server) {
int event_num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if (ret < 0) {
perror("epoll_wait() error\n");
close(listenfd);
close(epollfd);
return 1;
}
for (int i = 0; i < event_num; ++i) {
if (events[i].data.fd == listenfd) { // 有新的连接
struct sockaddr_in client_address;
bzero(&client_address, sizeof(client_address));
socklen_t client_addrlen = sizeof(client_address);
// 注意非阻塞accept的典型的连接方式
int connfd = 0;
while((connfd = accept(listenfd, (struct sockaddr*)&client_address, \
&client_addrlen)) > 0) {
addfd(epollfd, connfd);
}
} else if (events[i].data.fd == pipefd[0] && events[i].events & EPOLLIN) {
char signals[MAX_SIGNAL_NUMBE];
bzero(signals, sizeof(signals));
ret = recv(pipefd[0], signals, sizeof(signals), 0);
if (ret < 0) {
perror("recv() error\n");
continue;
} else if (ret == 0) {
continue;
} else { // 处理信号类型
switch (signals[i]) {
// 匹配并处理有关信号
}
}
} else {
// 在这里处理其他epoll事件
}
}
}
printf("server stop\n");
close(epollfd);
close(listenfd);
return 0;
}