epoll编程实例客户端_Linux编程--epoll复用技术实现统一处理信号事件源(实例分析)...

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

信号是一种异步事件:

信号处理函数和程序的主循环是两条不同的执行路线。显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(为了避免一些竞态条件,信号在处理期间,系统不会再次触发它)太久

一种典型的解决办法是:把信号的主要处理逻辑放到程序的主循环中

当信号处理函数被触发时,它只是简单地通过主循环程序接收到信号,并把信号值传递给主循环

主循环再根据接收到的信号值执行目标信号对应的逻辑代码

信号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值

主循环使用I/O复用系统调用来监听管道的读端文件描述符上的可读时间

如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源

很多优秀的I/O框架和后台服务器程序都统一处理信号和I/O事件,比如LiBEVENT I/O框架库和xinetd超级服务

需要C/C++ Linux服务器架构师学习资料加qun获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

二、编码实现

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define LISTEM_NUM 5

#define MAX_EVENT_NUM 1024

int 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=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

{

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=recv(pipe_fd[0],signals,sizeof(signals),0);

if(recv_ret_value<=0)

continue;

else{

//每个信号值占1字节,所以按字节来逐个接收信号

for(int i=0;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信号,程序终止(与预期一致)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值