信號的一種處理模式----統一事件源

信號是一種異步事件:信號處理函數和程序的主循環是兩條不同的執行路線。我們希望信號處理函數儘快地執行完畢,以確保該信號不會屏蔽(爲了避免一些竟態條件,信號在處理期間,系統不會再次觸發它)太久。(由於信號集採用位圖這種數據結構,導致在我們屏蔽該信號期間 ,無論該信號到達多少次,我們只能記錄一次,所以在當前信號處理函數執行完後,該信號只能觸發一次,這當然不是我們希望的,所以信號處理函數執行的越快,越能避免這種問題。)

爲了使信號處理函數執行的速度變快,我們可以將處理函數的主邏輯放在程序的主循環中,當信號處理函數被觸發時,它只是簡單的通知主循環程序接收到信號,並把信號直接傳遞給主程序,主程序再根據接收到的信號值執行目標信號對應的邏輯代碼。

信號處理函數通過管道來將信號"傳遞"給主程序:信號處理函數往管道的寫段寫入信號值,主程序從管道的讀端讀出該信號值。主程序如何知道管道何時有數據可讀呢?我們通過I/O複用系統調用來監聽管道的讀端文件描述符上的可讀事件。這樣,信號事件就能和其他I/O事件一樣被處理,即統一事件源。

很多優秀的I/O框架庫和後臺服務程序都統一處理信號和I/O事件,比如Libevent I/O框架庫和xinetd超級服務。

以下代碼是統一事件源的一個簡單實現:

#include<stdio.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<errno.h>
#include<assert.h>
static int pipefd[2];
void addfd(int epollfd,int sockfd)
{
	epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = sockfd;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
}
void sig_handler(int sig)
{
	send(pipefd[1],(char*)&sig,1,0);
}
void addsig(int sig)
{
	struct sigaction sa;
	memset(&sa,'\0',sizeof(sa));
	sa.sa_handler = sig_handler;
	sigfillset(&sa.sa_mask);
	int ret = sigaction(sig,&sa,NULL);
	assert(ret != -1);
}
int main(int argc,const char* argv[])
{
	if(argc < 3)
	{
		printf("usage: %s ip_address port_number\n",argv[0]);
		exit(1);
	}

	const char *ip = argv[1];
	int port = atoi(argv[2]);

	struct sockaddr_in listenaddr,connaddr;
	socklen_t len = sizeof(connaddr);
	listenaddr.sin_family = AF_INET;
	listenaddr.sin_port = htons(port);
	inet_pton(AF_INET,ip,&listenaddr.sin_addr);

	int listenfd = socket(AF_INET,SOCK_STREAM,0);
	bind(listenfd,(struct sockaddr*)&listenaddr,sizeof(listenaddr));
	listen(listenfd,5);

	epoll_event events[1024];
	int epollfd = epoll_create(5);
	addfd(epollfd,listenfd);
	bool stop_server = false;

	socketpair(AF_UNIX,SOCK_STREAM,0,pipefd);
	addfd(epollfd,pipefd[0]);

	/*設置信號處理函數*/
	addsig(SIGHUP);
	addsig(SIGCHLD);
	addsig(SIGTERM);
	addsig(SIGINT);
	
	while(!stop_server)
	{
		int num = epoll_wait(epollfd,events,1024,-1);
		if(num < 0)
		{
			if(errno == EINTR)//慢速系統調用被中斷後重啓系統調用
				continue;
			perror("epoll_wait error:");
			exit(1);
		}

		for(int i = 0;i < num;i++)
		{
			int sockfd = events[i].data.fd;
			if(sockfd == listenfd)
			{
				int connfd = accept(sockfd,(struct sockaddr*)&connaddr,&len);
				addfd(epollfd,connfd);
			}
			else if(sockfd == pipefd[0])
			{
				char signals[1024];
				int ret = recv(sockfd,signals,sizeof(signals),0);
				if(ret == -1)
				{
					perror("recv error:");
					continue;
				}
				for(int i = 0;i < ret;i++)
				{
					switch(signals[i])
					{
						case SIGCHLD:
						{
							printf("signal is SIGCHLD\n");
							break;
						}
						case SIGHUP:
						{
							printf("signal is SIGHUP\n");
							break;
						}
						case SIGTERM:
						{
							printf("signal is SIGTERM\n");
							stop_server = true;
							break;
						}
						case SIGINT:
						{
							printf("signal is SIGINT\n");
							stop_server = true;
							break;
						}
					}
				}
			}
		}
	}
	printf("close fds\n");
	close(epollfd);
	close(listenfd);
	close(pipefd[0]);
	close(pipefd[1]);
	return 0;
}





參考:Linux高性能服務器編程 遊雙

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章