linux網路程式設計十八:統一事件源
阿新 • • 發佈:2019-02-07
訊號是一種非同步事件:訊號處理函式和程式的主迴圈是兩條不同的執行路線。
//統一事件源 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <assert.h> #include <fcntl.h> #include <pthread.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <signal.h> #define MAX_EVENT_NUMBER 1024 static int pipefd[2]; int setnonblocking(int fd); //設定非阻塞 void addfd(int epollfd, int fd); //新增描述符的事件 void sig_handler(int sig); //訊號處理函式 void addsig(int sig); //新增訊號處理 int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s port\n", basename(argv[0])); return 1; } int port = atoi(argv[1]); int ret = 0; int error; struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htonl(INADDR_ANY); int sockfd = socket(PF_INET, SOCK_STREAM, 0); if (sockfd == -1) return 1; printf("server start...\n"); //設定地址可重用 int reuse = 1; ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (ret == -1) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } printf("server reuseaddr success\n"); if ((bind(sockfd, (struct sockaddr*)&address, sizeof(address)) == -1) || (listen(sockfd, 5) == -1)) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } printf("server bind and listen success\n"); epoll_event events[MAX_EVENT_NUMBER]; int epollfd = epoll_create(5); if (epollfd == -1) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } addfd(epollfd, sockfd); //使用socketpair建立管道,註冊pipefd[0]上的可讀事件 ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); if (ret == -1) { error = errno; while ((close(sockfd) == -1) && (errno == EINTR)); errno = error; return 1; } setnonblocking(pipefd[1]); addfd(epollfd, pipefd[0]); //設定訊號處理 addsig(SIGHUP); addsig(SIGCHLD); addsig(SIGTERM); addsig(SIGINT); bool stop_server = false; while (!stop_server) { int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); if (number < 0 && errno != EINTR) { fprintf(stderr, "epoll failed\n"); break; } for (int i = 0; i < number; i++) { int listenfd = events[i].data.fd; if (listenfd == sockfd) { //處理新連線 struct sockaddr_in client_address; socklen_t client_addrlength = sizeof(client_address); int connfd = -1; while ( ((connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength)) == -1) && (connfd == EINTR) ); addfd(epollfd, connfd); } else if (listenfd == pipefd[0] && events[i].events & EPOLLIN) { //處理訊號 char signals[1024]; ret = recv(pipefd[0], signals, sizeof(signals), 0); if (ret == -1) continue; else if (ret == 0) continue; else { //每個訊號值佔1位元組,所以按位元組來逐個接收訊號 for (int i = 0; i < ret; i++) { switch(signals[i]) { case SIGCHLD: { fprintf(stderr, "recv SIGCHLD\n"); continue; break; } case SIGHUP: { fprintf(stderr, "recv SIGHUP\n"); continue; break; } case SIGTERM: { fprintf(stderr, "recv SIGTERM, close server\n"); stop_server = true; break; } case SIGINT: { fprintf(stderr, "recv SIGINT, close server\n"); stop_server = true; break; } default: break; } } } } else { } } } printf("close fds\n"); close(sockfd); close(pipefd[1]); close(pipefd[0]); return 0; } int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } void addfd(int epollfd, int fd) { epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd); } void sig_handler(int sig) { int save_errno = errno; int msg = sig; send(pipefd[1], (char*)&msg, 1, 0); //將訊號寫入管道,以通知主迴圈 errno = save_errno; } void addsig(int sig) { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset(&sa.sa_mask); assert(sigaction(sig, &sa, NULL) != -1); }
參考:《linux高效能伺服器程式設計》