将经典C语言epoll反应堆封装到一个类中
#include<stdio.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include<errno.h>
#include<string.h> //strerror
//epoll反应堆
//传统epoll模型 传入ev.data=lfd或cfd;
//epoll反应堆中用自定义的结构体替代了lfd和cfd,自定义结构体包括监听的文件描述符lfd或cfd,回调函数和参数指针
//epoll反应堆中 传入联合体(ev.data)的是一个自定义结构体指针myev 即 ev.data.ptr=myev;
//相应事件发生 通过指针调用相应的回调函数 events[i].data.ptr->call_back(arg);
//epoll反应堆 工作流程
// 监听可读事件(ET) ⇒ 数据到来 ⇒ 触发读事件 ⇒
// epoll_wait()返回 ⇒
// read完数据; 节点下树; 设置监听写事件和对应写回调函数; 节点上树(可读事件回调函数内)
// ⇒ 监听可写事件(ET) ⇒ 对方可读 ⇒ 触发事件 ⇒
// epoll_wait()返回 ⇒
// write数据; 节点下树; 设置监听读事件和对应可读回调函数; 节点上树(可写事件回调函数内)
// ⇒ 直到程序停止前一直这么交替循环
#define MAX_EVENTS 1024 //监听上限
#define SERVER_PORT 8888 //服务器端口号
#define BUFLEN 4096 //缓存区大小
//回调函数格式
typedef void(*CALL_BACK)(void* erptr,void* m_arg);
//自定义任务类
class My_events{
public:
//更改
void Myev_set(CALL_BACK callback){
call_back=callback;
}
//重载更改
void Myev_set(int fd,CALL_BACK callback){
call_back=callback;
m_fd=fd;
}
int m_fd; //监听的文件描述符
CALL_BACK call_back; //回调函数
int m_events=0; //监听事件类型 EPOLLIN EPOLLOUT
int m_status=0; //是否在监听 1在树上,0 不在
char m_buf[BUFLEN]="";
int m_buf_len=0;
time_t last_active; //每次加入红黑树时间
};
class Epoll_reactor{
public:
//构造函数,启动服务器
Epoll_reactor(){
server_run();
}
//添加事件
void eventadd(int events,My_events* myev);
//删除一个事件
void eventdel(My_events* myev);
//初始化套接字
void initlistensockte();
//三个回调函数
static void acceptcoonect(void* erptr,void* arg);
//可读事件回调函数 接受客户端发送的数据
static void recevdata(void* erptr,void* arg);
//可写事件回调函数 发送数据
static void senddata(void* erptr,void* arg);
//服务器运行
void server_run();
//红黑树文件描述符
int epfd;
//自定义任务类数组
My_events mg_events[MAX_EVENTS];
// 接受epoll_wait 返回
struct epoll_event evs[MAX_EVENTS];
private:
};
//添加一个文件描述符
void Epoll_reactor::eventadd(int events,My_events* myev){
struct epoll_event ev={0,{0}};
//传统epoll模型传入lfd或cfd,epoll反应堆传入自定义结构体指针
ev.data.ptr=myev;
ev.events=myev->m_events=events; //EPOLLIN 或EPOLLOUT
int op=0;
if(myev->m_status==0){ //若原来不再树上
op=EPOLL_CTL_ADD;
}
else{
printf("add error:already on tree\n");
return;
}
if(epoll_ctl(epfd,op,myev->m_fd,&ev)<0){
printf("\n event add/mod false [fd= %d] [events= %d] \n", myev->m_fd, myev->m_events);
}
else{
myev->m_status=1;
printf("event add succeed[fd=%d][events=%d]\n",myev->m_fd,myev->m_events);
}
}
//删除一个文件描述符
void Epoll_reactor::eventdel(struct My_events* myev){
if(myev->m_status!=1){ //树上没有这个文件操作符
return;
}
epoll_ctl(epfd,EPOLL_CTL_DEL,myev->m_fd,NULL);
myev->m_status=0;
}
//回调函数 接受连接
void Epoll_reactor::acceptcoonect(void* erptr,void* arg){
Epoll_reactor* er_ptr=(Epoll_reactor*)erptr;
My_events* myev=(My_events*)arg;
int flag = 0;
int i;
int cfd;
struct sockaddr_in cliaddr;
socklen_t len=sizeof(cliaddr);
if((cfd=accept(myev->m_fd,(struct sockaddr*)&cliaddr,&len))==-1) //注意:括号不能少了一对
{
if(errno!=EAGAIN&&errno!=EINTR){ //暂不处理
sleep(1);
}
printf("%s:accept,%s",__func__,strerror(errno));
return;
}
do
{ //从全局数组mg_events中找一个空闲位置i
for(i=0;i<MAX_EVENTS;++i){
if(er_ptr->mg_events[i].m_status==0) break;
}
if(i==MAX_EVENTS){
printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
break;
}
if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd设置为非阻塞
{
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
break;
}
//找到合适的自定义结构体后,将其添加到树上,监听读事件
//eventset(&mg_events[i],cfd,recevdata);
er_ptr->mg_events[i].Myev_set(cfd,recevdata); //必须将recevdata声明为static
er_ptr->eventadd(EPOLLIN,&er_ptr->mg_events[i]);
}while(0);
printf("new connect[%s:%d],[time:%ld],pos[%d]\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), er_ptr->mg_events[i].last_active, i);
}
//接受客户端发送的数据 可读事件回调函数
void Epoll_reactor::recevdata(void* erptr,void* arg){
printf("recevdata \n");
Epoll_reactor* er_ptr=(Epoll_reactor*)erptr;
My_events* myev=(My_events*)arg;
int len;
//printf("cfd: %d,myev.m_fd: %d\n",cfd,myev->m_fd);
len=recv(myev->m_fd,myev->m_buf,sizeof(myev->m_buf),0);
//1.将该事件从红黑树删除
er_ptr->eventdel(myev);
if(len>0){
myev->m_buf_len=len;
myev->m_buf[len]='\0';//手动添加结束标记
printf("CLient[%d]: %s",myev->m_fd,myev->m_buf);
//2.设置cfd对应的回调函数为发送数据
myev->Myev_set(senddata);
//3.将cfd上树,监听写事件
er_ptr->eventadd(EPOLLOUT,myev);
}
else if(len==0){ //客户端关闭连接
close(myev->m_fd);
er_ptr->eventdel(myev);
printf("\n [Client:%d] disconnection \n", myev->m_fd);
}
else
{
close(myev->m_fd);
er_ptr->eventdel(myev);
printf("\n error: [Client:%d] disconnection\n", myev->m_fd);
}
}
//发送数据,可写事件回调函数
void Epoll_reactor::senddata(void* erptr,void* arg){
printf("senddata \n");
Epoll_reactor* er_ptr=(Epoll_reactor*)erptr;
My_events* myev=(My_events*)arg;
int len;
len=send(myev->m_fd,myev->m_buf,myev->m_buf_len,0); //回写
//1.将该事件从红黑树删除
er_ptr->eventdel(myev);
if(len>0){
//2.设置cfd对应的回调函数为读取数据
myev->Myev_set(recevdata);
//3.cfd上树,监听读事件
er_ptr->eventadd(EPOLLIN,myev);
}
else{
close(myev->m_fd);
er_ptr->eventdel(myev);
printf("\n send[fd=%d] error \n", myev->m_fd);
}
}
//初始化监听套接字
void Epoll_reactor::initlistensockte(){
int lfd;
struct sockaddr_in seraddr;
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //端口复用 消除TIME_WAIT
//创建监听套接字
lfd=socket(AF_INET,SOCK_STREAM,0);
//将监听套接字设置非阻塞
fcntl(lfd,F_SETFL,O_NONBLOCK);
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family=AF_INET;
seraddr.sin_addr.s_addr=INADDR_ANY;
seraddr.sin_port=htons(SERVER_PORT);
//绑定
bind(lfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
//监听上限
listen(lfd,128);
printf("Server Running:port[%d]\n", SERVER_PORT);
//lfd 初始化
mg_events[MAX_EVENTS-1].Myev_set(lfd,acceptcoonect);
//监听事件上树
eventadd(EPOLLIN,&mg_events[MAX_EVENTS-1]);
}
//服务器运行
void Epoll_reactor::server_run(){
//创建epoll对象
epfd=epoll_create(MAX_EVENTS);
if(epfd<0)
printf("create epfd in %s failed: %s \n",__func__,strerror(errno));
//初始化监听套接字
initlistensockte();
while(1){
int n_ready=epoll_wait(epfd,evs,MAX_EVENTS,-1);
if(n_ready<0){
printf("epoll_wait error!\n");
break;
}
for(int i=0;i<n_ready;++i){
My_events* myev=(My_events*)evs[i].data.ptr;
if(evs[i].events&EPOLLIN&&myev->m_events&EPOLLIN)
myev->call_back(this,myev);
if(evs[i].events&EPOLLOUT&&myev->m_events&EPOLLOUT)
myev->call_back(this,myev);
}
}
}
int main(){
Epoll_reactor epractr;
return 0;
}