IO多路复用 epoll 以及 epoll反应堆模型

一 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 + 反应堆模型
在这里插入图片描述

流程:

  1. epoll_create()
  2. 设置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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值