《Linux高性能服务器编程》学习总结——信号

Posted torrance

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Linux高性能服务器编程》学习总结——信号相关的知识,希望对你有一定的参考价值。

第十章      信号

  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: [email protected]
  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: [email protected]
 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接收带外数据的程序都不能接收,具体原因尚不知,待日后解决。

以上是关于《Linux高性能服务器编程》学习总结——信号的主要内容,如果未能解决你的问题,请参考以下文章

《Linux高性能服务器编程》学习总结——定时器

《Linux高性能服务器编程》学习总结——Linux网络编程基础API

《Linux高性能服务器编程》学习总结——TCP/IP协议族

《Linux高性能服务器编程》学习总结——Linux服务器程序规范

《Linux高性能服务器编程》学习总结——高级I/O函数

《Linux高性能服务器编程》学习总结——I/O复用