1. 程式人生 > >《Linux高性能服務器編程》學習總結(十)——信號

《Linux高性能服務器編程》學習總結(十)——信號

orm 分享圖片 body return lur signal函數 pac 網上 linu

第十章 信號

  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高性能服務器編程》學習總結(十)——信號