一 IO多路复用epoll相关接口
1.int epoll_create(int size);
功能:
创建一个epoll句柄
size: 用来告诉内核这个监听的数目一共有多大,参数 size 并不是限制了 epoll 所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议值;
2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对要操作的文件fd进行上树或者下树操作;
epfd: 是epoll_create()返回值
op:表示对fd的操作,配置通常如下
EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从 epfd 中删除一个 fd;
fd:带操作的fd文件句柄
event:告诉内核要监听什么事件
EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET :将 EPOLL 设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
其中:
typedef union epoll_data {
void ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
// 感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; / Epoll events /
epoll_data_t data; / User data variable */
};
3.int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout );
等待事件的产生,收集在 epoll 监控的事件中已经发送的事件,
epfd:
红黑树的根节点;
events:
内核会将准备好的事件放在events数组中;
maxevents:
maxevents 告之内核这个 events 有多大
timeouts:
超时时间,-1,函数阻塞等待
二. epoll的一般用法
int main(int argc,char *argv)
{
// [1] 创建一个epoll对象
epfd = epoll_create(OPEN_MAX); /* 创建epoll模型,ep_fd指向红黑树根节点 */
listen_ep_event.events = EPOLLIN; /* 指定监听读事件 注意:默认为水平触发LT */
listen_ep_event.data.fd = listen_fd; /* 注意:一般的epoll在这里放fd */
// [2] 将listen_fd和对应的结构体设置到树上
epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &listen_ep_event);
while(1) {
// [3] 为server阻塞(默认)监听事件,ep_event是数组,装满足条件后的所有事件结构体
n_ready = epoll_wait(ep_fd, ep_event, OPEN_MAX, -1);
for(i=0; i<n_ready; i++) {
temp_fd = ep_event[i].data.fd;
if(ep_event[i].events & EPOLLIN){
if(temp_fd == listen_fd) { //说明有新连接到来
connect_fd = accept(listen_fd, (struct sockaddr *)&client_socket_addr, &client_socket_len);
// 给即将上树的结构体初始化
temp_ep_event.events = EPOLLIN;
temp_ep_event.data.fd = connect_fd;
// 上树
epoll_ctl(ep_fd, EPOLL_CTL_ADD, connect_fd, &temp_ep_event);
}
else { //cfd有数据到来
n_data = read(temp_fd , buf, sizeof(buf));
if(n_data == 0) { //客户端关闭
epoll_ctl(ep_fd, EPOLL_CTL_DEL, temp_fd, NULL) //下树
close(temp_fd);
}
else if(n_data < 0) {}
do {
//处理数据
}while( (n_data = read(temp_fd , buf, sizeof(buf))) >0 ) ;
}
}
else if(ep_event[i].events & EPOLLOUT){
//处理写事件
}
else if(ep_event[i].events & EPOLLERR) {
//处理异常事件
}
}
}
close(listen_fd);
close(ep_fd);
}
三. epoll + 反应堆模型
流程:
- epoll_create()
- 设置lfd:将lfd 放在事件数组内(将fd给自定义的结构体数组元素赋值,并且给lfd设置回调处理函数)
自定义的结构体如下:
/*定义就绪文件描述符*/
typedef struct
{
int fd; //文件描述符
int events; //对应的监听事件,EPOLLIN和EPLLOUT
void *arg; //回调函数参数
FuncCallBack fcallback; //回调函数
int status; //是否在监听 0不在树上; 1在树上
char buf[BUF_SIZE];
int len;
int last_active;
}Event_T;
3.将自定义的结构体上树
就是将自定义的结构体赋值给
struct epoll_event 中data 域中的ptr指针即:
ev.data.ptr = pstEvent; //ptr指向自定义的结构体(之前红黑树使用的时候是指向lfd或者cfd)
ev.events = pstEvent->events = events;
4.作为服务器调用epoll_wait等待客户端连接
epoll_wait返回的就是那些满足监听条件的fd上的struct epoll_events; 因为上面我们已经为每个fd设置好了回调处理函数,所以对返回的满足监听条件的fd的struct epoll_events 中的data域中的ptr会就是指向我们自定义的结构体类型,我们可直接调用回调函数进行处理。
Event_T *ev = (Event_T *)events[i].data.ptr;
//如果监听的是读事件,并返回的是读事件
if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
{
ev->fcallback(ev->fd, events[i].events, ev->arg);
}
//如果监听的是写事件,并返回的是写事件
if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
{
ev->fcallback(ev->fd, events[i].events, ev->arg);
}
整体代码如下:
#include "common.h"
#include "wrap.h"
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <arpa/inet.h>
#define MAX_EVENT_SIZE (1024)
#define BUF_SIZE (4 * 1024)
typedef void(*FuncCallBack)(int fd, int events, void *arg);
void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void* arg);
/*定义就绪文件描述符*/
typedef struct
{
int fd; //文件描述符
int events; //对应的监听事件,EPOLLIN和EPLLOUT
void *arg; //回调函数参数
FuncCallBack fcallback; //回调函数
int status; //是否在监听 0不在树上; 1在树上
char buf[BUF_SIZE];
int len;
int last_active;
}Event_T;
static Event_T g_steventSet[MAX_EVENT_SIZE + 1];
static int g_efd = 0;
/*
*@brief 封装一个自定义事件fd, fd的回调函数、参数等
*@fd 待处理的文件描述符
*@pstEvent 封装的自定义事件
*@fcallback fd的回调函数
*@arg 回调函数的参数
*return void
*/
int eventSet(int fd, Event_T* pstEvent, FuncCallBack fcallback, void *arg)
{
pstEvent->fd = fd;
pstEvent->events = 0;
pstEvent->fcallback = fcallback;
pstEvent->arg = arg;
pstEvent->status = 0;
if(pstEvent->len <= 0)
{
memset(pstEvent->buf, 0, sizeof(pstEvent->buf));
pstEvent->len = 0;
}
pstEvent->last_active = time(NULL); //调用eventset函数的时间
return 0;
}
/*
*@将自定义事件上树
*@ efd 红黑树根节点
*@ events 监听事件类型(EPOLLIN/EPOLLOUT)
*@ pstEvent 自定义事件类型
* return
*/
int eventadd(int efd, int events, Event_T * pstEvent)
{
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
int opt = 0;
ev.data.ptr = pstEvent; //ptr指向自定义的结构体(之前红黑树使用的时候是指向lfd或者cfd)
ev.events = pstEvent->events = events;
if(!pstEvent->status)
{
opt = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
pstEvent->status = 1;
}
if(epoll_ctl(efd, opt, pstEvent->fd, &ev) < 0) //将pstEvent->fd 节点上树
{
DBG("err:epoll_ctr add failed\n");
return -1;
}
else
{
DBG("epoll_ctr sucess\n");
}
return 0;
}
/*发送数据到客户端*/
void senddata(int fd, int events, void* arg)
{
Event_T *ev = (Event_T *)arg;
int len = Write(fd,ev->buf, ev->len);
//eventDel();
if (len > 0)
{
DBG("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
eventSet(fd, ev, recvdata, ev); //将该fd的回调函数改为recvdata
eventadd(g_efd, EPOLLIN, ev); //重新添加到红黑树上,设为监听读事件
}
else
{
Close(ev->fd); //关闭链接
DBG("send[fd=%d] error\n", fd);
}
return ;
}
/*读取客户端发过来的数据*/
void recvdata(int fd, int events, void *arg)
{
Event_T *ev = (Event_T *)arg;
int len = Read(fd, ev->buf, sizeof(ev->buf));
//将该节点从树上删除
//eventdel();
if(len > 0)
{
ev->len = len;
ev->buf[len] = '\0';
DBG("buf:[%d] %s\n",fd,ev->buf);
eventSet(fd, ev, senddata, ev); //设置fd对应的回调函数
eventadd(g_efd, EPOLLOUT, ev); //将fd假如红黑树中 监听写事件
}
else if (len == 0)
{
Close(ev->fd);
/* ev-g_events 地址相减得到偏移元素位置 */
DBG("[fd=%d] closed\n", fd);
}
else
{
Close(ev->fd);
DBG("recv[fd=%d]\n", fd);
}
}
/*
* @brief 处理客户端连接
* @fd 监听lfd
* @events
* @arg
*/
void acceptConn(int fd, int events, void *arg)
{
struct sockaddr_in caddr; //客户端地址
socklen_t len = sizeof(struct sockaddr_in); //地址长度
int cfd = 0;
int i = 0;
cfd = Accept(fd, (struct sockaddr*)&caddr, &len);
do
{
for(i = 0; i < MAX_EVENT_SIZE; i++)
{
if(!g_steventSet[i].status) //(查找空闲的结构体元素)如果自定义事件结构体数组中status = 0
{
break;
}
}
if(i == MAX_EVENT_SIZE)
{
DBG("max connect limit:%d\n",MAX_EVENT_SIZE);
break;
}
int flag = fcntl(cfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL,flag);
eventSet(cfd,&g_steventSet[i],recvdata,&g_steventSet[i]);
eventadd(g_efd, EPOLLIN, &g_steventSet[i]);
}while(0);
return ;
}
/*初始化epfd,创建socket,添加lfd*/
int initListenSocket(int efd, int port)
{
struct sockaddr_in addr;
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
//将lfd设置为非阻塞
fcntl(lfd, F_SETFL,O_NONBLOCK);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
Bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
Listen(lfd, 128);
eventSet(lfd,&g_steventSet[MAX_EVENT_SIZE], acceptConn, &g_steventSet[MAX_EVENT_SIZE]);
//将lfd上树
eventadd(efd, EPOLLIN, &g_steventSet[MAX_EVENT_SIZE]); //将lfd添加到监听树上,监听读事件
}
int test(void)
{
int port = 6666;
g_efd = epoll_create(MAX_EVENT_SIZE);
if(g_efd < 0)
{
DBG("err:epoll_create\n");
return -1;
}
//初始化监听的epfd
initListenSocket(g_efd,port);
struct epoll_event events[MAX_EVENT_SIZE + 1]; //定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体
DBG("server running:port[%d]\n", port);
int checkpos = 0;
int i;
while(1)
{
/* long now = time(NULL);
for(i=0; i < 100; i++, checkpos++)
{
if(checkpos == MAX_EVENTS);
checkpos = 0;
if(g_events[checkpos].status != 1)
continue;
long duration = now -g_events[checkpos].last_active;
if(duration >= 60)
{
close(g_events[checkpos].fd);
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]);
}
} */
//调用eppoll_wait等待接入的客户端事件,epoll_wait传出的是满足监听条件的那些fd的struct epoll_event类型
int nfd = epoll_wait(g_efd, events, MAX_EVENT_SIZE+1, 1000);
if (nfd < 0)
{
DBG("epoll_wait error, exit\n");
exit(-1);
}
for(i = 0; i < nfd; i++)
{
//evtAdd()函数中,添加到监听树中监听事件的时候将myevents_t结构体类型给了ptr指针
//这里epoll_wait返回的时候,同样会返回对应fd的myevents_t类型的指针
Event_T *ev = (Event_T *)events[i].data.ptr;
//如果监听的是读事件,并返回的是读事件
if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
{
ev->fcallback(ev->fd, events[i].events, ev->arg);
}
//如果监听的是写事件,并返回的是写事件
if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
{
ev->fcallback(ev->fd, events[i].events, ev->arg);
}
}
}
return 0;
}