epoll 反应堆模型:
epoll ET模式 + 非阻塞、轮询 + void *ptr。
普通epoll模型创建流程
原来: socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足 – read() — 小->大 – write回去 }
epoll反应堆模型创建流程
socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足
– read() — 小->大 – cfd从监听红黑树上摘下 – EPOLLOUT – 回调函数 – epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听写事件
– 等待 epoll_wait 返回 – 说明 cfd 可写 – write回去 – cfd从监听红黑树上摘下 – EPOLLIN
– epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听读事件 – epoll_wait 监听 }
总结: 也就是说反应堆做的事是**不但要监听 cfd 的读事件、还要监听cfd的写事件。
**
反应堆的理解:加入IO转接之后,有了事件,server才去处理,这里反应堆也是这样,由于网络环境复杂,服务器处理数据之后,可能并不能直接写回去,比如遇到网络繁忙或者对方缓冲区已经满了这种情况,就不能直接写回给客户端。反应堆就是在处理数据之后,监听写事件,能写会客户端了,才去做写回操作。写回之后,再改为监听读事件。如此循环。
实现代码:
#include "wrap.h"
#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <time.h>
#include <fcntl.h>
#define MAX_EVENTS 1024
#define SERV_PORT 9527
#define BUFLEN 1024
void revcdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);
//定义 my_events结构体
struct myevent_s
{
int fd; //要监听的文件描述符
int events; //对应的监听事件
void *arg; //泛型参数
void (*call_back)(int fd, int events, void *arg); //回调函数
int status; //是否在监听,1->在红黑树上监听, 0->不在
char buf[BUFLEN]; //处理缓冲区的数组
int len; //从缓冲区读到的长度
long last_active; //记录每次加入红黑树 g_efd的时间,
};
//定义epoll_create返回的文件描述符
int g_efd = 0;
struct myevent_s g_events[MAX_EVENTS+1]; //自定义结构体类型数组,最后一个指向listenfd
//将结构体 myevent_s 成员变量初始化
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
//memset(ev->buf, 0, sizeof(ev->buf));
//ev->len = 0;
ev->last_active = time(NULL); //调用eventset函数的时间
}
//向epoll监听的红黑树 g_efd中添加一个文件描述符
void eventadd(struct myevent_s *ev, int efd, int events)
{
struct epoll_event tempEp = {
0, {
0}};
int op;
tempEp.data.ptr = ev; //有事件加入时, ptr就=ev 可以事件调用的函数
tempEp.events = ev->events = events;
//如果不在红黑树中
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
}
else {
op = EPOLL_CTL_ADD;
ev->status = 1;
}
if(epoll_ctl(efd, op, ev->fd, &tempEp) < 0)
perr_exit("epoll_ctl faild\n");
else
printf("event add ok [fd=%d], op=%d, event[%0X]\n", ev->fd, op, events);
}
//从红黑树中删除节点
void eventdel(int efd, struct myevent_s *ev)
{
struct epoll_event epv = {
0, {
0}};
if(ev->status == 0)
return ;
epv.data.ptr = ev;
ev->status = 0;
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
printf("del fd:%d things:%d\n", ev->fd, ev->events);
}
//读事件 调用函数
void recvdata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0); //最后参数为0 可以把函数recv看作 read
eventdel(g_efd, ev); //将该节点从红黑树中删除
if