【Linux(程序设计):58---epoll复用技术实现统一处理信号事件源】千磨万击还坚劲,任尔东西南北风。这篇文章主要讲述Linux(程序设计):58---epoll复用技术实现统一处理信号事件源相关的知识,希望能为你提供帮助。
一、统一信号处理事件源概述
- 信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(为了避免一些竞态条件,信号在处理期间,系统不会再次触发它)太久
-
一种典型的解决办法是:
- 把信号的主要处理逻辑放到程序的主循环中
- 当信号处理函数被触发时,它只是简单地通过主循环程序接收到信号,并把信号值传递给主循环
- 主循环再根据接收到的信号值执行目标信号对应的逻辑代码
-
信号处理函数通常使用管道来将信号“传递”给主循环:
- 信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值
- 主循环使用I/O复用系统调用来监听管道的读端文件描述符上的可读时间
- 如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源
- 很多优秀的I/O框架和后台服务器程序都统一处理信号和I/O事件,比如LiBEVENT I/O框架库和xinetd超级服务
#include <
stdio.h>
#include <
stdlib.h>
#include <
strings.h>
#include <
unistd.h>
#include <
fcntl.h>
#include <
errno.h>
#include <
libgen.h>
#include <
signal.h>
#include <
sys/socket.h>
#include <
sys/epoll.h>
#include <
sys/types.h>
#include <
arpa/inet.h>
#define LISTEM_NUM 5
#define MAX_EVENT_NUM 1024int setnonblocking(int fd);
void add_epoll_fd(int epoll_fd,int fd);
void sig_handler(int sigalno);
void add_sig(int sigalno);
static int pipe_fd[2];
int main(int argc,char* argv[])
{
if(argc!=3){
printf("usage:./%s [server ip] [server port]\\n",basename(argv[1]));
exit(EXIT_FAILURE);
}int ser_fd,server_port;
const char* server_ip;
//创建套接字
if((ser_fd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("socket");
exit(EXIT_FAILURE);
}//初始化服务端地址
struct sockaddr_in server_address;
server_ip=argv[1];
server_port=atoi(argv[2]);
bzero(&
server_address,sizeof(server_address));
server_address.sin_family=AF_INET;
server_address.sin_port=htons(server_port);
if(inet_pton(AF_INET,server_ip,&
server_address.sin_addr.s_addr)==-1){
perror("inet_pton");
exit(EXIT_FAILURE);
}//绑定服务端地址
if(bind(ser_fd,(struct sockaddr*)&
server_address,sizeof(server_address))==-1){
perror("bind");
exit(EXIT_FAILURE);
}//开启监听
if(listen(ser_fd,LISTEM_NUM)==-1){
perror("bind");
exit(EXIT_FAILURE);
}int epoll_fd;
//创建epoll事件表句柄
if((epoll_fd=epoll_create(5))==-1){
perror("epoll_create");
exit(EXIT_FAILURE);
}
//将服务端套接字加入到事件表中
add_epoll_fd(epoll_fd,ser_fd);
//创建管道
if(socketpair(PF_UNIX,SOCK_STREAM,0,pipe_fd)==-1){
perror("socketpair");
exit(EXIT_FAILURE);
}
/*sockpair函数创建的管道是全双工的,不区分读写端
此处我们假设pipe_fd[1]为写端,非阻塞
pipe_fd[0]为读端
*/
setnonblocking(pipe_fd[1]);
add_epoll_fd(epoll_fd,pipe_fd[0]);
//为一些信号绑定信号处理函数
add_sig(SIGHUP);
//终端接口检测到一个连接断开,发送此信号
add_sig(SIGCHLD);
//子进程终止或停止时,子进程发送此信号
add_sig(SIGTERM);
//接收到kill命令
add_sig(SIGINT);
//用户按下中断键(Delete或Ctrl+C)int server_running=1;
int epoll_wait_ret_value;
struct epoll_event events[MAX_EVENT_NUM];
while(server_running)
{
bzero(events,sizeof(events));
epoll_wait_ret_value=https://www.songbingjia.com/android/epoll_wait(epoll_fd,events,MAX_EVENT_NUM,-1);
//epoll_wait函数出错
if((epoll_wait_ret_value==-1)&
&
(errno!=EINTR)){
close(ser_fd);
perror("epoll_wait");
exit(EXIT_FAILURE);
}//遍历就绪的事件
for(int i=0;
i<
epoll_wait_ret_value;
++i)
{
int sock_fd=events[i].data.fd;
//获取文件描述符
//如果是服务端套接字,接收客户端的连接
if(sock_fd==ser_fd){
int client_fd;
char client_address_ip[24];
struct sockaddr_in client_address;
socklen_t address_len=sizeof(client_address);
bzero(&
client_address,sizeof(client_address));
if((client_fd=accept(ser_fd,(struct sockaddr*)&
client_address,&
address_len))==-1){
perror("accept");
continue;
}//将新的客户端套接字放入到事件集中
add_epoll_fd(epoll_fd,client_fd);
//打印客户端地址信息
inet_ntop(AF_INET,&
client_address.sin_addr.s_addr,client_address_ip,sizeof(client_address_ip));
printf("get a new client,ip:%s,port:%d\\n",client_address_ip,ntohs(client_address.sin_port));
}
//如果是管道的一端有数据可读,那么处理信号
else if((sock_fd==pipe_fd[0])&
&
(events[i].events&
EPOLLIN)){
char signals[1024];
int sig;
int recv_ret_value;
recv_ret_value=https://www.songbingjia.com/android/recv(pipe_fd[0],signals,sizeof(signals),0);
if(recv_ret_value<
=0)
continue;
else{
//每个信号值占1字节,所以按字节来逐个接收信号
for(int i=0;
i<
recv_ret_value;
++i){
printf("server:I caugh the signal %d\\n", signals[i]);
switch (signals[i])
{
case SIGCHLD:
case SIGHUP:
continue;
//接收到下面这两个信号,终止程序
case SIGTERM: //kill
case SIGINT://ctrl +c
server_running=0;
}
}
}
}
//如果是客户端
else{}
}
}printf("service is down\\n");
close(ser_fd);
close(pipe_fd[1]);
close(pipe_fd[0]);
exit(EXIT_SUCCESS);
}int setnonblocking(int fd)
{
int old_options,new_options;
//获取原先的描述符标志
if((old_options=fcntl(fd,F_GETFL))==-1){
perror("fcntl");
exit(EXIT_FAILURE);
}
//设置非阻塞
new_options=old_options|O_NONBLOCK;
if(fcntl(fd,F_SETFL,new_options)==-1){
perror("fcntl");
exit(EXIT_FAILURE);
}return old_options;
}void add_epoll_fd(int epoll_fd,int fd)
{
struct epoll_event new_event;
bzero(&
new_event,sizeof(new_event));
new_event.events=EPOLLIN|EPOLLET;
new_event.data.fd=fd;
//将新事件加入到事件表中
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&
new_event)==-1){
perror("epoll_ctl");
exit(EXIT_FAILURE);
}//设置为非阻塞
setnonblocking(fd);
}void add_sig(int sigalno)
{
struct sigaction act;
bzero(&
act,sizeof(act));
act.sa_handler=sig_handler;
//设置信号处理函数
sigfillset(&
act.sa_mask);
//初始化信号屏蔽集
act.sa_flags|=SA_RESTART;
//由此信号中断的系统调用自动重启动//初始化信号处理函数
if(sigaction(sigalno,&
act,NULL)==-1){
printf("capture signal,but to deal with failure\\n");
return;
}
}void sig_handler(int sigalno)
{
printf("capture signal,signal num is %d",sigalno);
//保留原来的errno,在函数最后回复,以保证函数的可重入性
int save_errno=errno;
int msg=sigalno;
//将信号值写入管代,通知主循环
if(send(pipe_fd[1],(char*)&
msg,1,0)<
=0){
printf("The message sent to the server failed\\n");
}
printf(",signal is send to server\\n");
errno=save_errno;
}
代码解析
- 创建一个无名管道,管道[0]端用来读取数据,[1]端用来发送数据。读写端都设置为非阻塞
- 当信号处理函数执行时,在处理函数中向[1]端发送信号编号
- 主函数使用epoll轮询,其中包括轮询管道[0],一旦有信息(信号编号)发来,处理信号
代码演示
- 使用客户端工具连接程序,打印客户端连接信息
文章图片
- 使用kill命令给服务端程序发送一个编码为1的信号,可以看到服务端接收到这个信号
文章图片
- 按下Ctrl+C触发SIGINT信号,程序终止(与预期一致)
文章图片
推荐阅读
- ES6深度解析-Classes
- Linux(内核剖析):31---内核同步之(信号量(semaphore)读写信号量(rw_semaphore))
- MySQL数据库进阶篇
- 基于Sharding Sphere实现数据“一键脱敏”
- MySQL 数据库 增删查改克隆外键 等操作
- 经过我慎重考虑,决定把我自学Java基础的教程分享出来,一定收藏!
- Vlan之间的通信详解
- MySQL 到底是如何做到多版本并发的()
- 深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存