Linux网络编程 | 信号 :信号函数、信号集、统一事件源 、网络编程相关信号
文章目錄
- 信號(hào)函數(shù)
- 信號(hào)集
- 統(tǒng)一事件源
- 網(wǎng)絡(luò)編程相關(guān)信號(hào)
Linux 進(jìn)程信號(hào):信號(hào)的概念、生命周期、產(chǎn)生流程、阻塞
在半年前我寫(xiě)過(guò)一篇博客介紹了Linux中信號(hào)的概念以及處理流程,這次再來(lái)深入的講一講信號(hào)的具體使用方法以及在網(wǎng)絡(luò)編程中的具體應(yīng)用。
信號(hào)函數(shù)
要想為一個(gè)信號(hào)設(shè)置處理函數(shù),可以使用以下兩個(gè)系統(tǒng)調(diào)用。
signal
signal函數(shù)的使用非常簡(jiǎn)單,只需要直接指定信號(hào)類(lèi)型以及處理的方法即可
#include <signal.h>typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);參數(shù)
sighandler_t:函數(shù)指針,返回值為void,參數(shù)為int的函數(shù)
signum:要捕獲的信號(hào)類(lèi)型
handler:指定的信號(hào)處理函數(shù)
?
返回值
調(diào)用成功:signal函數(shù)調(diào)用成功的時(shí)候會(huì)返回一個(gè)函數(shù)指針,函數(shù)指針的值為前一次調(diào)用signal函數(shù)時(shí)傳入的函數(shù)指針,或者是信號(hào)sig對(duì)應(yīng)的默認(rèn)處理函數(shù)指針SIG_DEF(如果時(shí)第一次調(diào)用的話)
調(diào)用失敗:signal函數(shù)調(diào)用出錯(cuò)時(shí)返回SIG_ERR,并設(shè)置errno
sigaction
sigaction比起signal要更加的健壯。
#include <signal.h>int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);參數(shù)
signum:信號(hào)類(lèi)型
act:指定新的信號(hào)處理方法
oact:輸出該信號(hào)之前的處理方法
?
返回值
成功:返回0
失敗:返回1并設(shè)置errno
從上面可以看到act和oldact都是sigaction結(jié)構(gòu)體指針,下面來(lái)看看它里面有什么內(nèi)容
struct sigaction {void (*sa_handler)(int); //信號(hào)的處理方法void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; //設(shè)置進(jìn)程的信號(hào)掩碼,是一個(gè)信號(hào)集int sa_flags; //設(shè)置程序收到信號(hào)后的行為,參數(shù)如下圖void (*sa_restorer)(void); //已過(guò)時(shí),最好不要使用 };信號(hào)集
#include<bits/sigset.h> #define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))typedef struct {unsigned long sig[_NSIG_WORDS]; } sigset_t上面是信號(hào)集的定義,從上面可以看出,其實(shí)信號(hào)集就是用于設(shè)置信號(hào)掩碼的一個(gè)位圖,他的每一個(gè)位都用來(lái)表示一個(gè)信號(hào)。
下面是信號(hào)集的基本操作
#include <signal.h> //清空信號(hào)集 int sigemptyset(sigset_t *set); //在信號(hào)集中設(shè)置所有信號(hào) int sigfillset(sigset_t *set);//將signum信號(hào)添加進(jìn)信號(hào)集中 int sigaddset(sigset_t *set, int signum);//將signum信號(hào)從信號(hào)集中刪除 int sigdelset(sigset_t *set, int signum);//查看signum信號(hào)在不在信號(hào)集中 int sigismember(const sigset_t *set, int signum);我們還可以通過(guò)以下函數(shù)來(lái)查看進(jìn)程的信號(hào)掩碼
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);參數(shù)
set:設(shè)置新的信號(hào)掩碼
oldset:數(shù)據(jù)原來(lái)的信號(hào)掩碼
how:設(shè)置信號(hào)掩碼的方式
?
返回值
成功:返回0
失敗:返回-1并設(shè)置errno
當(dāng)我們?cè)O(shè)置完信號(hào)掩碼之后,被屏蔽的信號(hào)就不能被進(jìn)程接收,如果向進(jìn)程發(fā)送一個(gè)被屏蔽的信號(hào),則操作系統(tǒng)會(huì)將該信號(hào)設(shè)置為掛起信號(hào),直到進(jìn)程取消對(duì)該信號(hào)的屏蔽的時(shí)候再處理這個(gè)信號(hào)
同時(shí),有兩個(gè)信號(hào)比較特殊,9號(hào)信號(hào)SIGKILL和19號(hào)信號(hào)SIGSTOP,這兩個(gè)信號(hào)不可被阻塞,不可被忽略,不可被自定義處理。
使用下面這個(gè)函數(shù)可以獲得當(dāng)前被掛起的信號(hào)集
因?yàn)閽炱鹦盘?hào)使用信號(hào)集來(lái)進(jìn)行存儲(chǔ),而信號(hào)集又是一個(gè)位圖,這就意味著即使一個(gè)信號(hào)多次被接收,在位圖中也只能反應(yīng)一次,并且我們結(jié)束屏蔽后對(duì)掛起信號(hào)進(jìn)行處理時(shí),也只會(huì)觸發(fā)一次信號(hào)處理函數(shù)
參數(shù)
set:用于保存被掛起的信號(hào)集
?
返回值
成功:返回0
失敗:返回-1并設(shè)置errno
統(tǒng)一事件源
信號(hào)是一種異步事件,所以信號(hào)處理函數(shù)和程序的主循環(huán)是兩條不同的執(zhí)行路線。很顯然,信號(hào)處理函數(shù)需要盡可能快地執(zhí)行完畢,確保該信號(hào)不會(huì)被屏蔽太久。(為了避免一些競(jìng)態(tài)條件,信號(hào)在處理期間,系統(tǒng)不會(huì)再次觸發(fā)他)
一種典型的解決方案是把信號(hào)的主要處理邏輯放到程序地主循環(huán)中,當(dāng)信號(hào)處理函數(shù)被觸發(fā),它只是簡(jiǎn)單的通知主循環(huán)程序接收到信號(hào),并把信號(hào)值傳遞給主循環(huán),主循環(huán)從讀取出該信號(hào),并根據(jù)具體接收到的信號(hào)值執(zhí)行對(duì)應(yīng)的邏輯代碼。
信號(hào)處理函數(shù)通常都會(huì)使用管道來(lái)作為信號(hào)傳遞的媒介,那么主循環(huán)如何直到管道什么時(shí)候有數(shù)據(jù)呢?很簡(jiǎn)單,只需要用I/O復(fù)用來(lái)監(jiān)聽(tīng)管道讀端的可讀事件,這樣一來(lái)信號(hào)事件就可以和其他I/O事件一樣被處理,這就是統(tǒng)一事件源。在Libevent庫(kù)與xinetd超級(jí)服務(wù)中也采用了這樣的機(jī)制
代碼實(shí)現(xiàn)如下
#include<fcntl.h> #include<signal.h> #include<stdlib.h> #include<sys/socket.h> #include<sys/epoll.h> #include<sys/types.h> #include<arpa/inet.h> #include<stdio.h> #include<errno.h> #include<netinet/in.h> #include<unistd.h>const int MAX_LISTEN = 5; const int MAX_EVENT = 1024; const int MAX_BUFFER = 1024; static int pipefd[2]; //管道描述符//設(shè)置非阻塞 int setnonblocking(int fd) {int flag = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flag |= O_NONBLOCK);return flag; }//信號(hào)處理函數(shù) void sig_handler(int sig) {//保留原本的errno, 再函數(shù)末尾恢復(fù), 確??芍厝胄?/span>int save_errno = errno;send(pipefd[1], (char*)&sig, 1, 0); //將信號(hào)值通過(guò)管道發(fā)送給主循環(huán)errno = save_errno; }//設(shè)置信號(hào)處理函數(shù) void set_sig_handler(int sig) {struct sigaction sa;sa.sa_handler = sig_handler;sa.sa_flags |= SA_RESTART; //重新調(diào)用被信號(hào)中斷的系統(tǒng)函數(shù)sigfillset(&sa.sa_mask); //將所有信號(hào)加入信號(hào)掩碼中if(sigaction(sig, &sa, NULL) < 0){exit(EXIT_FAILURE);} }//將描述符加入epoll監(jiān)聽(tīng)集合中 void epoll_add_fd(int epoll_fd, int fd) {struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); }int main(int argc, char*argv[]) {if(argc <= 2){printf("輸入?yún)?shù):IP地址 端口號(hào)\n");return 1;}const char* ip = argv[1];int port = atoi(argv[2]);//創(chuàng)建監(jiān)聽(tīng)套接字int listen_fd = socket(PF_INET, SOCK_STREAM, 0); if(listen_fd == -1){printf("listen_fd socket.\n");return -1;}//綁定地址信息struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);if(bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0){printf("listen_fd bind.\n");return -1;}//開(kāi)始監(jiān)聽(tīng)if(listen(listen_fd, MAX_LISTEN) < 0){printf("listen_fd listen.\n");return -1;}//創(chuàng)建epoll,現(xiàn)版本已忽略大小,給多少都無(wú)所謂int epoll_fd = epoll_create(MAX_LISTEN);if(epoll_fd == -1){printf("epoll create.\n");return -1;}epoll_add_fd(epoll_fd, listen_fd); //將監(jiān)聽(tīng)套接字加入epoll中//使用sockpair創(chuàng)建全雙工管道,對(duì)讀端進(jìn)行監(jiān)控,統(tǒng)一事件源if(socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd) < 0){printf("socketpair.\n");return -1;}setnonblocking(pipefd[1]); //將寫(xiě)端設(shè)為非阻塞epoll_add_fd(epoll_fd, pipefd[0]); //將讀端加入epoll監(jiān)控集合set_sig_handler(SIGHUP); //有連接脫離終端(斷開(kāi))時(shí)發(fā)送的信號(hào)set_sig_handler(SIGCHLD); //子進(jìn)程退出時(shí)發(fā)送的信號(hào)set_sig_handler(SIGINT); //接收到kill命令 set_sig_handler(SIGTERM); //用戶按下中斷鍵(DELETE或者Ctrl+C)int stop_server = 0;struct epoll_event events[MAX_LISTEN];while(!stop_server){int number = epoll_wait(epoll_fd, events, MAX_LISTEN, -1);if(number < 0 && errno != EINTR){printf("epoll_wait.\n");break;}for(int i = 0; i < number; i++){int sock_fd = events[i].data.fd;//如果監(jiān)聽(tīng)套接字就緒則處理連接if(sock_fd == listen_fd){struct sockaddr_in clinet_addr;socklen_t len = sizeof(clinet_addr);if(accept(listen_fd, (struct sockaddr*)&clinet_addr, &len) < 0){printf("accept.\n");continue;}epoll_add_fd(epoll_fd, sock_fd);}//如果就緒的是管道的讀端,則說(shuō)明有信號(hào)到來(lái),要處理信號(hào)else if(sock_fd == pipefd[0] && events[i].events & EPOLLIN){int sig;char signals[MAX_BUFFER];int ret = recv(pipefd[0], signals, MAX_BUFFER, 0);if(ret == -1){continue;}else if(ret == 0){continue;}else{//由于一個(gè)信號(hào)占一個(gè)字節(jié),所以按字節(jié)逐個(gè)處理信號(hào)for(int j = 0; j < ret; j++){switch (signals[i]){//這兩個(gè)信號(hào)主要是某個(gè)連接或者子進(jìn)程退出,對(duì)主流程影響不大,直接忽略case SIGCHLD:case SIGHUP:{continue;}//這兩個(gè)信號(hào)主要是強(qiáng)制中斷主流程,所以結(jié)束服務(wù)(不能直接退出,因?yàn)檫€有描述符未關(guān)閉)case SIGTERM:case SIGINT:{stop_server = 1;}}}}}//處理讀就緒與寫(xiě)就緒,因?yàn)檫@里主要演示統(tǒng)一事件源,所以不實(shí)現(xiàn)這塊的邏輯else{/* */}}}//關(guān)閉文件描述符close(listen_fd);close(pipefd[1]);close(pipefd[0]);return 0; }網(wǎng)絡(luò)編程相關(guān)信號(hào)
SIGHUP
當(dāng)某個(gè)進(jìn)程脫離終端時(shí)(正?;蛘叻钦?#xff09;,SIGHUP信號(hào)都將被觸發(fā)。該信號(hào)的默認(rèn)處理方式是終止收到該信號(hào)的進(jìn)程,因此如果沒(méi)有捕捉該信號(hào),則進(jìn)程就會(huì)退出。在網(wǎng)絡(luò)編程中通常使用SIGHUP信號(hào)來(lái)強(qiáng)制服務(wù)器重讀配置文件。
SIGPIPE
SIGPIPE是往一個(gè)讀端關(guān)閉的描述符寫(xiě)數(shù)據(jù)時(shí)通知的信號(hào),例如我們往一個(gè)讀端關(guān)閉的管道或者socket連接中寫(xiě)數(shù)據(jù)就會(huì)引發(fā)SIGPIPE信號(hào),并且將errno設(shè)置為EPIPE。
由于程序接收到SIGPIPE信號(hào)就會(huì)默認(rèn)結(jié)束進(jìn)程,所以我們需要在代碼中捕獲并且處理該信號(hào),或至少忽略它,因?yàn)槲覀兺ǔ2粫?huì)希望因?yàn)殄e(cuò)誤的寫(xiě)操作而導(dǎo)致程序退出
SIGURG
在Linux環(huán)境下,內(nèi)核通知應(yīng)用程序帶外數(shù)據(jù)到達(dá)主要有兩種方法,一種是I/O復(fù)用如select等系統(tǒng)調(diào)用在接收到帶外數(shù)據(jù)的時(shí)候就會(huì)返回,并且會(huì)向應(yīng)用程序報(bào)告socket上的異常事件。另一種方法就是使用SIGURG信號(hào)。
總結(jié)
以上是生活随笔為你收集整理的Linux网络编程 | 信号 :信号函数、信号集、统一事件源 、网络编程相关信号的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux网络编程 | 并发模式:半同步
- 下一篇: Linux网络编程 | 定时事件 :Li