飞道的博客

Linux(服务器编程):25---epoll复用技术实现统一处理信号事件源

438人阅读  评论(0)

一、统一信号处理事件源概述

  • 信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(为了避免一些竞态条件,信号在处理期间,系统不会再次触发它)太久
  • 一种典型的解决办法是:
    • 把信号的主要处理逻辑放到程序的主循环中
    • 当信号处理函数被触发时,它只是简单地通过主循环程序接收到信号,并把信号值传递给主循环
    • 主循环再根据接收到的信号值执行目标信号对应的逻辑代码
  • 信号处理函数通常使用管道来将信号“传递”给主循环:
    • 信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值
    • 主循环使用I/O复用系统调用来监听管道的读端文件描述符上的可读时间
  • 如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源
  • 很多优秀的I/O框架和后台服务器程序都统一处理信号和I/O事件,比如LiBEVENT I/O框架库和xinetd超级服务

二、编码实现


  
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <strings.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <errno.h>
  7. #include <libgen.h>
  8. #include <signal.h>
  9. #include <sys/socket.h>
  10. #include <sys/epoll.h>
  11. #include <sys/types.h>
  12. #include <arpa/inet.h>
  13. #define LISTEM_NUM 5
  14. #define MAX_EVENT_NUM 1024
  15. int setnonblocking(int fd);
  16. void add_epoll_fd(int epoll_fd,int fd);
  17. void sig_handler(int sigalno);
  18. void add_sig(int sigalno);
  19. static int pipe_fd[ 2];
  20. int main(int argc,char* argv[])
  21. {
  22. if(argc!= 3){
  23. printf( "usage:./%s [server ip] [server port]\n",basename(argv[ 1]));
  24. exit(EXIT_FAILURE);
  25. }
  26. int ser_fd,server_port;
  27. const char* server_ip;
  28. //创建套接字
  29. if((ser_fd=socket(AF_INET,SOCK_STREAM, 0))== -1){
  30. perror( "socket");
  31. exit(EXIT_FAILURE);
  32. }
  33. //初始化服务端地址
  34. struct sockaddr_in server_address;
  35. server_ip=argv[ 1];
  36. server_port=atoi(argv[ 2]);
  37. bzero(&server_address, sizeof(server_address));
  38. server_address.sin_family=AF_INET;
  39. server_address.sin_port=htons(server_port);
  40. if(inet_pton(AF_INET,server_ip,&server_address.sin_addr.s_addr)== -1){
  41. perror( "inet_pton");
  42. exit(EXIT_FAILURE);
  43. }
  44. //绑定服务端地址
  45. if(bind(ser_fd,(struct sockaddr*)&server_address, sizeof(server_address))== -1){
  46. perror( "bind");
  47. exit(EXIT_FAILURE);
  48. }
  49. //开启监听
  50. if(listen(ser_fd,LISTEM_NUM)== -1){
  51. perror( "bind");
  52. exit(EXIT_FAILURE);
  53. }
  54. int epoll_fd;
  55. //创建epoll事件表句柄
  56. if((epoll_fd=epoll_create( 5))== -1){
  57. perror( "epoll_create");
  58. exit(EXIT_FAILURE);
  59. }
  60. //将服务端套接字加入到事件表中
  61. add_epoll_fd(epoll_fd,ser_fd);
  62. //创建管道
  63. if(socketpair(PF_UNIX,SOCK_STREAM, 0,pipe_fd)== -1){
  64. perror( "socketpair");
  65. exit(EXIT_FAILURE);
  66. }
  67. /*sockpair函数创建的管道是全双工的,不区分读写端
  68. 此处我们假设pipe_fd[1]为写端,非阻塞
  69. pipe_fd[0]为读端
  70. */
  71. setnonblocking(pipe_fd[ 1]);
  72. add_epoll_fd(epoll_fd,pipe_fd[ 0]);
  73. //为一些信号绑定信号处理函数
  74. add_sig(SIGHUP); //终端接口检测到一个连接断开,发送此信号
  75. add_sig(SIGCHLD); //子进程终止或停止时,子进程发送此信号
  76. add_sig(SIGTERM); //接收到kill命令
  77. add_sig(SIGINT); //用户按下中断键(Delete或Ctrl+C)
  78. int server_running= 1;
  79. int epoll_wait_ret_value;
  80. struct epoll_event events[MAX_EVENT_NUM];
  81. while(server_running)
  82. {
  83. bzero(events, sizeof(events));
  84. epoll_wait_ret_value=epoll_wait(epoll_fd,events,MAX_EVENT_NUM, -1);
  85. //epoll_wait函数出错
  86. if((epoll_wait_ret_value== -1)&&(errno!=EINTR)){
  87. close(ser_fd);
  88. perror( "epoll_wait");
  89. exit(EXIT_FAILURE);
  90. }
  91. //遍历就绪的事件
  92. for( int i= 0;i<epoll_wait_ret_value;++i)
  93. {
  94. int sock_fd=events[i].data.fd; //获取文件描述符
  95. //如果是服务端套接字,接收客户端的连接
  96. if(sock_fd==ser_fd){
  97. int client_fd;
  98. char client_address_ip[ 24];
  99. struct sockaddr_in client_address;
  100. socklen_t address_len= sizeof(client_address);
  101. bzero(&client_address, sizeof(client_address));
  102. if((client_fd=accept(ser_fd,(struct sockaddr*)&client_address,&address_len))== -1){
  103. perror( "accept");
  104. continue;
  105. }
  106. //将新的客户端套接字放入到事件集中
  107. add_epoll_fd(epoll_fd,client_fd);
  108. //打印客户端地址信息
  109. inet_ntop(AF_INET,&client_address.sin_addr.s_addr,client_address_ip, sizeof(client_address_ip));
  110. printf( "get a new client,ip:%s,port:%d\n",client_address_ip,ntohs(client_address.sin_port));
  111. }
  112. //如果是管道的一端有数据可读,那么处理信号
  113. else if((sock_fd==pipe_fd[ 0])&&(events[i].events&EPOLLIN)){
  114. char signals[ 1024];
  115. int sig;
  116. int recv_ret_value;
  117. recv_ret_value=recv(pipe_fd[ 0],signals, sizeof(signals), 0);
  118. if(recv_ret_value<= 0)
  119. continue;
  120. else{
  121. //每个信号值占1字节,所以按字节来逐个接收信号
  122. for( int i= 0;i<recv_ret_value;++i){
  123. printf( "server:I caugh the signal %d\n", signals[i]);
  124. switch (signals[i])
  125. {
  126. case SIGCHLD:
  127. case SIGHUP:
  128. continue;
  129. //接收到下面这两个信号,终止程序
  130. case SIGTERM: //kill
  131. case SIGINT: //ctrl +c
  132. server_running= 0;
  133. }
  134. }
  135. }
  136. }
  137. //如果是客户端
  138. else{
  139. }
  140. }
  141. }
  142. printf( "service is down\n");
  143. close(ser_fd);
  144. close(pipe_fd[ 1]);
  145. close(pipe_fd[ 0]);
  146. exit(EXIT_SUCCESS);
  147. }
  148. int setnonblocking(int fd)
  149. {
  150. int old_options,new_options;
  151. //获取原先的描述符标志
  152. if((old_options=fcntl(fd,F_GETFL))== -1){
  153. perror( "fcntl");
  154. exit(EXIT_FAILURE);
  155. }
  156. //设置非阻塞
  157. new_options=old_options|O_NONBLOCK;
  158. if(fcntl(fd,F_SETFL,new_options)== -1){
  159. perror( "fcntl");
  160. exit(EXIT_FAILURE);
  161. }
  162. return old_options;
  163. }
  164. void add_epoll_fd(int epoll_fd,int fd)
  165. {
  166. struct epoll_event new_event;
  167. bzero(&new_event, sizeof(new_event));
  168. new_event.events=EPOLLIN|EPOLLET;
  169. new_event.data.fd=fd;
  170. //将新事件加入到事件表中
  171. if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&new_event)== -1){
  172. perror( "epoll_ctl");
  173. exit(EXIT_FAILURE);
  174. }
  175. //设置为非阻塞
  176. setnonblocking(fd);
  177. }
  178. void add_sig(int sigalno)
  179. {
  180. struct sigaction act;
  181. bzero(&act, sizeof(act));
  182. act.sa_handler=sig_handler; //设置信号处理函数
  183. sigfillset(&act.sa_mask); //初始化信号屏蔽集
  184. act.sa_flags|=SA_RESTART; //由此信号中断的系统调用自动重启动
  185. //初始化信号处理函数
  186. if(sigaction(sigalno,&act, NULL)== -1){
  187. printf( "capture signal,but to deal with failure\n");
  188. return;
  189. }
  190. }
  191. void sig_handler(int sigalno)
  192. {
  193. printf( "capture signal,signal num is %d",sigalno);
  194. //保留原来的errno,在函数最后回复,以保证函数的可重入性
  195. int save_errno=errno;
  196. int msg=sigalno;
  197. //将信号值写入管代,通知主循环
  198. if(send(pipe_fd[ 1],( char*)&msg, 1, 0)<= 0){
  199. printf( "The message sent to the server failed\n");
  200. }
  201. printf( ",signal is send to server\n");
  202. errno=save_errno;
  203. }

代码解析

  • 创建一个无名管道,管道[0]端用来读取数据,[1]端用来发送数据。读写端都设置为非阻塞
  • 当信号处理函数执行时,在处理函数中向[1]端发送信号编号
  • 主函数使用epoll轮询,其中包括轮询管道[0],一旦有信息(信号编号)发来,处理信号

代码演示

  • 使用客户端工具连接程序,打印客户端连接信息

  • 使用kill命令给服务端程序发送一个编码为1的信号,可以看到服务端接收到这个信号

  • 按下Ctrl+C触发SIGINT信号,程序终止(与预期一致)


转载:https://blog.csdn.net/qq_41453285/article/details/103685562
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场