第十章 信号
Linux中信号是由用户、系统或进程发送给目标进程的信息,用来通知进程某个状态的改变或系统异常,其产生条件如下:1)对于前台进程,用户可以通过输入特殊的终端字符来发送信号,比如Ctrl+C发送中断信号;2)系统异常;3)系统状态变化,如SIGALRM信号;4)运行kill命令或使用kill函数。服务器程序必须处理一些常见的信号,以避免异常终止。
我们来看一下kill函数:
1 #include<sys/types.h> 2 #include<signal.h> 3 int kill(pid_t pid, int sig);
在这个函数中,如果pid大于0则给pid为这个值的进程发送信号;如果pid等于0则发送给本进程组内的其他进程;如果pid等于-1则给除init进程外的所有进程,但发送者需要由给目标进程发送信号的权限;如果pid小于-1则向组id为-pid的进程组中的所有成员发送。后面的sig参数则是发送的信号种类,Linux中的信号族中有很多信号,这里就不一一说了。
而接受信号的函数则是signal函数,可以指定一个接收函数来处理接收到的信号,也可以使用SIG_DFL和SIG_IGN两种预定义的宏来处理,分别代表采用默认方式处理和忽略。还有一种更加健壮的信号处理系统调用sigaction,其参数中需要指定新的信号处理方式,是一个sigset_t类型的结构体,其中包括信号掩码、信号处理函数,信号集等参数。
信号掩码用来设置进程可以接收什么信号,当进程设置了信号掩码之后,若收到了被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起信号,如果取消被挂起信号的屏蔽就可以马上接收到这个信号。
信号是一种异步事件,其处理函数和主循环是两条不同的执行路线,而信号处理函数必须尽快执行,以确保信号不被屏蔽,原因是信号在处理期间系统不会触发该信号,为了避免某些竞态条件。一个解决办法就是把信号的主要处理逻辑放在主函数内,信号处理函数只负责将信号值传递给主循环,这样我们就可以用I/O复用来监听信号事件:
1 /************************************************************************* 2 > File Name: 10-1.cpp 3 > Author: Torrance_ZHANG 4 > Mail: 597156711@qq.com 5 > Created Time: Sat 10 Feb 2018 02:56:14 AM PST 6 ************************************************************************/ 7 8 #include"head.h" 9 using namespace std; 10 11 #define MAX_EVENT_NUMBER 1024 12 static int pipefd[2]; 13 14 int setnonblocking(int fd) { 15 int old_option = fcntl(fd, F_GETFL); 16 int new_option = old_option | O_NONBLOCK; 17 fcntl(fd, F_SETFL, new_option); 18 return old_option; 19 } 20 21 void addfd(int epollfd, int fd) { 22 epoll_event event; 23 event.data.fd = fd; 24 event.events = EPOLLIN | EPOLLET; 25 epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 26 setnonblocking(fd); 27 } 28 29 void sig_handler(int sig) { 30 int save_errno = errno; 31 int msg = sig; 32 send(pipefd[1], (char*)&msg, 1, 0); 33 errno = save_errno; 34 } 35 36 void addsig(int sig) { 37 struct sigaction sa; 38 memset(&sa, 0, sizeof(sa)); 39 sa.sa_handler = sig_handler; 40 sa.sa_flags |= SA_RESTART; 41 sigfillset(&sa.sa_mask); 42 assert(sigaction(sig, &sa, NULL) != -1); 43 } 44 45 int main(int argc, char* argv[]) { 46 if(argc <= 2) { 47 printf("usage: %s ip_address port_number\n", basename(argv[0])); 48 return 1; 49 } 50 const char* ip = argv[1]; 51 int port = atoi(argv[2]); 52 53 int ret = 0; 54 struct sockaddr_in address; 55 bzero(&address, sizeof(address)); 56 address.sin_family = AF_INET; 57 inet_pton(AF_INET, ip, &address.sin_addr); 58 address.sin_port = htons(port); 59 60 int listenfd = socket(AF_INET, SOCK_STREAM, 0); 61 assert(listenfd >= 0); 62 63 ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address)); 64 if(ret == -1) { 65 printf("errno is %d\n", errno); 66 return 1; 67 } 68 ret = listen(listenfd, 5); 69 assert(ret != -1); 70 71 epoll_event events[MAX_EVENT_NUMBER]; 72 int epollfd = epoll_create(5); 73 assert(epollfd != -1); 74 addfd(epollfd, listenfd); 75 76 ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); 77 assert(ret != -1); 78 setnonblocking(pipefd[1]); 79 addfd(epollfd, pipefd[0]); 80 81 addsig(SIGHUP); 82 addsig(SIGCHLD); 83 addsig(SIGTERM); 84 addsig(SIGINT); 85 bool stop_server = false; 86 87 while(!stop_server) { 88 int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); 89 if((number < 0) && (errno != EINTR)) { 90 printf("epoll failure\n"); 91 break; 92 } 93 for(int i = 0; i < number; i ++) { 94 int sockfd = events[i].data.fd; 95 if(sockfd == listenfd) { 96 struct sockaddr_in client_address; 97 socklen_t client_addrlength = sizeof(client_address); 98 int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength); 99 addfd(epollfd, connfd); 100 } 101 else if((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) { 102 int sig; 103 char signals[1024]; 104 ret = recv(pipefd[0], signals, sizeof(signals), 0); 105 if(ret == -1) continue; 106 else if(ret == 0) continue; 107 else { 108 for(int i = 0; i < ret; i ++) { 109 switch(signals[i]) { 110 case SIGCHLD: 111 case SIGHUP: continue; 112 case SIGTERM: 113 case SIGINT: stop_server = true; 114 } 115 } 116 } 117 } 118 else {} 119 } 120 } 121 printf("close fds\n"); 122 close(listenfd); 123 close(pipefd[1]); 124 close(pipefd[0]); 125 return 0; 126 }
上例演示了如何安全终止服务器的主循环。
在网络编程中,有三个重要的信号。SIGHUP对于网络后台服务器而言一般用于强制服务器重读配置文件;SIGPIPE的产生一般是当往一个读端关闭的管道或socket写数据会引发,而程序一旦接收到SIGPIPE信号就会结束进程,所以我们一般都会捕获或者忽略之;最后就是SIGURG信号,它是另外一种通知应用进程有带外数据的方法,我们来通过一个程序看一下如何处理:
1 /************************************************************************* 2 > File Name: 10-3.cpp 3 > Author: Torrance_ZHANG 4 > Mail: 597156711@qq.com 5 > Created Time: Sat 10 Feb 2018 03:45:14 AM PST 6 ************************************************************************/ 7 8 #include"head.h" 9 using namespace std; 10 11 #define BUF_SIZE 1024 12 static int connfd; 13 14 void sig_urg(int sig) { 15 int save_errno = errno; 16 char buffer[BUF_SIZE]; 17 memset(buffer, 0, sizeof(buffer)); 18 int ret = recv(connfd, buffer, BUF_SIZE, MSG_OOB); 19 printf("got %d bytes of oob data ‘%s‘\n", ret, buffer); 20 errno = save_errno; 21 } 22 23 void addsig(int sig, void (*sig_handler)(int)) { 24 struct sigaction sa; 25 memset(&sa, 0, sizeof(sa)); 26 sa.sa_handler = sig_handler; 27 sa.sa_flags |= SA_RESTART; 28 sigfillset(&sa.sa_mask); 29 assert(sigaction(sig, &sa, NULL) != -1); 30 } 31 32 int main(int argc, char* argv[]) { 33 if(argc <= 2) { 34 printf("usage: %s ip_address port_number\n", basename(argv[0])); 35 return 1; 36 } 37 const char* ip = argv[1]; 38 int port = atoi(argv[2]); 39 40 struct sockaddr_in address; 41 bzero(&address, sizeof(address)); 42 address.sin_family = AF_INET; 43 inet_pton(AF_INET, ip, &address.sin_addr); 44 address.sin_port = htons(port); 45 46 int sock = socket(AF_INET, SOCK_STREAM, 0); 47 assert(sock >= 0); 48 49 int ret = bind(sock, (struct sockaddr*)&address, sizeof(address)); 50 assert(ret != -1); 51 52 ret = listen(sock, 5); 53 assert(ret != -1); 54 55 struct sockaddr_in client_address; 56 socklen_t client_addrlength = sizeof(client_address); 57 connfd = accept(sock, (struct sockaddr*)&client_address, &client_addrlength); 58 59 if(connfd < 0) printf("errno is: %d\n", errno); 60 else { 61 addsig(SIGURG, sig_urg); 62 fcntl(connfd, F_SETOWN, getpid()); 63 64 char buffer[BUF_SIZE]; 65 while(1) { 66 memset(buffer, 0, sizeof(buffer)); 67 ret = recv(connfd, buffer, BUF_SIZE, 0); 68 if(ret <= 0) { 69 break; 70 } 71 printf("got %d bytes of normal data ‘%s‘\n", ret, buffer); 72 } 73 close(connfd); 74 } 75 close(sock); 76 return 0; 77 }
此程序在Ubuntu16.04下有问题,不能接收到带外数据,在网上找了其他利用SIGURG接收带外数据的程序都不能接收,具体原因尚不知,待日后解决。