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