对于epoll的实现的简单理解和反应堆模型的实现

理解了中断、等待队列、调度,你就能懂Linux的80%


写给自己 : 发现自己对于很多知识都只是仅仅了解一哈,不做深入了解,很多Bug出现自己都不知道为什么?
这和工具人有什么区别?

我们要了解epoll如何实现,首先需要了解关于内核的三点操作,什么是等待队列,内核的poll机制

一.等待队列

在这里简单解释一下队列头是生产者,队列成员是消费者,当头的资源ready后,逐渐执行每一个成员的回调函数.
具体思想就是一个任务在某个wait_queue_head上睡眠,将自己的进程控制块信息封装到wait_queue上,然后挂载到wait_queue的链表之中,执行调度睡眠,事件发生之后,会唤醒这个任务

struct __wait_queue_head {
      spinlock_t lock;                    /* 保护等待队列的原子锁 */
      struct list_head task_list;          /* 等待队列 *
};


__wait_queue,该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:

struct __wait_queue {

 unsigned int flags;

 void *private;                       /* 通常指向当前任务控制块 */

/* 任务唤醒操作方法,该方法在内核中提供,通常为autoremove_wake_function */

 wait_queue_func_t func;             

 struct list_head task_list;              /* 挂入wait_queue_head的挂载点 */

};

在这里插入图片描述
二.内核的poll机制

我们都知道poll机制的作用主要是要通过查询驱动设备是不是非阻塞的,既然要查询,那么poll机制本身都是非阻塞的,这里我们得使用等待队列机制,我们首先需要将进poll 函数的进程,进程加入到等待队列
,进行休眠等待,在等待阶段,如果有新的数据到来,会被唤醒,不然会在时间到达之后进行唤醒

当应用程序调用poll函数的时候,会调用系统调用sys_poll函数,之后会调用do_poll函数,do_poll函数又会调用do_pollfd函数,调用中的poll_wait函数,把进程挂到阻塞队列之中,确定相关的fd有没有内容可读,如果可读就返回1

三.相关的内核知识

  • fd对应的是struct file 结构,可以看作内核态的文件描述符
  • spinlock,自旋锁,线程反复检查锁变量是不是可用,因为线程在这一过程中保持执行,一旦获取了自旋锁,线程会一致保持该锁,直到释放,自旋锁避免了进程上下文的调度开销
  • 引用计数,struct file 是持有引用计数的,一般等引用计数为0的时候进程才被调用

四.epoll api 的简单分析

epoll_create

  • 当有一个进程调用了epoll_cretae 的时候,内核会创建一个eventpoll结构体,这个也可以被称为内核态的epollfd

struct eventpoll
 {
  spinlock_t lock ; //自旋锁   
   /添加, 修改或者删除监听fd的时候, 以及epoll_wait返回, 向用户空间      传递数据时都会持有这个互斥锁, 所以在用户空间可以放心的在多个线程
     中同时执行epoll相关的操作, 内核级已经做了保护. */
    struct mutex mtx;
    /* Wait queue used by sys_epoll_wait() */
    /* 调用epoll_wait()时, 我们就是"睡"在了这个等待队列上... */
    wait_queue_head_t wq;
    /* Wait queue used by file->poll() */
    /* 这个用于epollfd被poll的时候... */
    wait_queue_head_t poll_wait;
    /* List of ready file descriptors */
    /* 所有已经ready的epitem都在这个链表里面 */
    struct list_head rdllist;
    /* RB tree root used to store monitored fd structs */
    /* 所有要监听的epitem都在这里 */
   struct rb_root rbr; // 红黑树的根
   	这是一个单链表链接着所有的struct epitem当event转移到用户空间时
    struct epitem *ovflist;
    /* The user that created the eventpoll descriptor */
    /* 这里保存了一些用户变量, 比如fd监听数量的最大值等等 */
    struct user_struct *user;
    };

其中最重要的是struct rb_root rbrstruct list_head rdliist 第一个初始化红黑树的结点,这个书里面存储所有添加到epoll中的事件 , 双向链表rdlist 保存着将要通过的epoll_wait返回给用户的事件,这个链表中只保留活跃的事件

我们通过查看eopll_create的源码

//先进行判断size是否>=0,若是则直接调用epoll_create1
SYSCALL_DEFINE1(epoll_create, int, size)
{
        if (size <= 0)
                return -EINVAL;
        return sys_epoll_create1(0);
}

可能有很多同学看到这里就很困惑,为什么是一个宏,主要是因为系统调用的参数个数,传参方式都有一些严格的限制,我们可以很清楚的看到sys_epoll_create 最后调用的函数还是 epoll_create1 微笑脸

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error;
    struct eventpoll *ep = NULL;//主描述符
    /* Check the EPOLL_* constant for consistency.  */
    /* 这句没啥用处... */
    BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);
    /* 对于epoll来讲, 目前唯一有效的FLAG就是CLOEXEC */
    //这个表示的意思就是进程执行exec之后,描述符号会被关闭
    if (flags & ~EPOLL_CLOEXEC)
        return -EINVAL;
    /*
     * Create the internal data structure ("struct eventpoll").
     */
    error = ep_alloc(&ep);  //开始分配一个struct eventpoll
    if (error < 0)
        return error;
    /*
     * Creates all the items needed to setup an eventpoll file. That is,
     * a file structure and a free file descriptor.
     */
    /* 这里是创建一个匿名fd, 说起来就话长了...长话短说:
     * epollfd本身并不存在一个真正的文件与之对应, 所以内核需要创建一个
     * "虚拟"的文件, 并为之分配真正的struct file结构, 而且有真正的fd.
     * 这里2个参数比较关键:
     * eventpoll_fops, fops就是file operations, 就是当你对这个文件(这里是虚拟的)进行操作(比如读)时,
     * fops里面的函数指针指向真正的操作实现, 类似C++里面虚函数和子类的概念.
     * epoll只实现了poll和release(就是close)操作, 其它文件系统操作都有VFS全权处理了.
     * ep, ep就是struct epollevent, 它会作为一个私有数据保存在struct file的private指针里面.
     * 其实说白了, 就是为了能通过fd找到struct file, 通过struct file能找到eventpoll结构.
     * 如果懂一点Linux下字符设备驱动开发, 这里应该是很好理解的,
     * 推荐阅读 <Linux device driver 3rd>
     */
    error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
                 O_RDWR | (flags & O_CLOEXEC));
    if (error < 0)
        ep_free(ep);
    return error;
}

所有添加到epoll中的事件会给设备建立一个回调关系,这个回调关系在内核中叫ep_poll_callback ,它会把这样的事件放到上面的rdlist双向链表中

- 对于添加到epoll中的每一个事件,都会建立一个epitem结构体

struct epitem {  
    // 挂载到eventpoll 的红黑树节点  
    struct rb_node rbn;  
    // 挂载到eventpoll.rdllist 的节点  
    struct list_head rdllink;  
    // 连接到ovflist 的指针  
    struct epitem *next;  
    /* 文件描述符信息fd + file, 红黑树的key */  
    struct epoll_filefd ffd;  
    /* Number of active wait queue attached to poll operations */  
    int nwait;  
    // 当前文件的等待队列(eppoll_entry)列表  
    // 同一个文件上可能会监视多种事件,  
    // 这些事件可能属于不同的wait_queue中  
    // (取决于对应文件类型的实现),  
    // 所以需要使用链表  
    struct list_head pwqlist;  
    // 指向所属的eventpoll  
    struct eventpoll *ep;  
    /* List header used to link this item to the &quot;struct file&quot; items list */  
    struct list_head fllink;  
  //用户所期待的事件类型
    struct epoll_event event;  
};

epoll_ctl

每次我们执行epoll_wait检查有没有发生事件的连接的时候,就只是检查eventpoll对象中的双向链表中有没有元素,从用户态复制给内核态,这里有一点很重要,很多博客说是采用什么共享内存什么鬼的,我们可以从源码来看看,到底采用了什么?


SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
{
	  int error;
	   struct file *file, *tfile;
	   struct eventpoll *ep;
	   //创建eventpoll 指针 ,红黑树的根,双向链表
	   struct epitem *epi;//
	   struct epoll_event epds;
	   error = -EFAULT;
/*
 * 错误处理以及从用户空间拷贝给内核态
 */
 //在这里我们可以很清楚的看出调用的是copy_from_usr函数
 //ep_op_has_event 看是不是有注册的事件
if (ep_op_has_event(op) &&
    copy_from_user(&epds, event, sizeof(struct epoll_event)))
    goto error_return;
/* Get the "struct file *" for the eventpoll file */
/* 取得struct file结构, epfd既然是真正的fd, 那么内核空间
 * 就会有与之对于的一个struct file结构
 * 这个结构在epoll_create1()中, 由函数anon_inode_getfd()分配 */
error = -EBADF;
file = fget(epfd);
if (!file)
    goto error_return;
/* Get the "struct file *" for the target file */
/* 我们需要监听的fd, 它当然也有个struct file结构, 上下2个不要搞混了哦 */
tfile = fget(fd);
if (!tfile)
    goto error_fput;
/* The target file descriptor must support poll */
error = -EPERM;
/* 如果监听的文件不支持poll, 那就没辙了.
 */
if (!tfile->f_op || !tfile->f_op->poll)
    goto error_tgt_fput;
/*
 * We have to check that the file structure underneath the file descriptor
 * the user passed to us _is_ an eventpoll file. And also we do not permit
 * adding an epoll file descriptor inside itself.
 */
error = -EINVAL;
/* epoll不能自己监听自己... */
if (file == tfile || !is_file_epoll(file))
    goto error_tgt_fput;
/*
 * At this point it is safe to assume that the "private_data" contains
 * our own data structure.
 */
/* 取到我们的eventpoll结构, 来自与epoll_create1()中的分配 */
ep = file->private_data;
/* 接下来的操作有可能修改数据结构内容, 锁之~ */
mutex_lock(&ep->mtx);
/*
 * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
 * above, we can be sure to be able to use the item looked up by
 * ep_find() till we release the mutex.
 */
/* 对于每一个监听的fd, 内核都有分配一个epitem结构,
 * 而且我们也知道, epoll是不允许重复添加fd的,
 * 所以我们首先查找该fd是不是已经存在了.
 * ep_find()其实就是RBTREE查找, 跟C++STL的map差不多一回事, O(lgn)的时间复杂度.
 */
epi = ep_find(ep, tfile, fd);
error = -EINVAL;
switch (op) {
    /* 首先我们关心添加 */
case EPOLL_CTL_ADD:
    if (!epi) {
        /* 之前的find没有找到有效的epitem, 证明是第一次插入, 接受!
         * 这里我们可以知道, POLLERR和POLLHUP事件内核总是会关心的
         * */
        epds.events |= POLLERR | POLLHUP;
        /* ... */
        error = ep_insert(ep, &epds, tfile, fd);
    } else
        /* 找到了!? 重复添加! */
        error = -EEXIST;
    break;
    /* 删除和修改操作都比较简单 */
case EPOLL_CTL_DEL:
    if (epi)
        error = ep_remove(ep, epi);
    else
        error = -ENOENT;
    break;
case EPOLL_CTL_MOD:
    if (epi) {
        epds.events |= POLLERR | POLLHUP;
        error = ep_modify(ep, epi, &epds);
    } else
        error = -ENOENT;
    break;
}
    mutex_unlock(&ep->mtx);
    error_tgt_fput:
    fput(tfile);
    error_fput:
    fput(file);
    error_return:
    return error;

}


总结 也就是epoll_create 之后,创建了红黑树和就绪链表,在等待队列上有进程挂起,到时候进行唤醒,在内核中创建cache

static int __init eventpoll_init(void)  
{  
    struct sysinfo si;  //sysinfo 用来获取系统信息
    //
    struct sysinfo {
      long uptime;          /* 启动到现在经过的时间 */
      unsigned long loads[3];  
      /* 1, 5, and 15 minute load averages */
      unsigned long totalram;  /* 总的可用的内存大小 */
      unsigned long freeram;   /* 还未被使用的内存大小 */
      unsigned long sharedram; /* 共享的存储器的大小*/
      unsigned long bufferram; /* 缓冲区的大小 */
      unsigned long totalswap; /* 交换区大小 */
      unsigned long freeswap;  /* 还可用的交换区大小 */
      unsigned short procs;    /* 当前进程数目 */
      unsigned long totalhigh; /* 总的高内存大小 */
      unsigned long freehigh;  /* 可用的高内存大小 */
      unsigned int mem_unit;   /* 以字节为单位的内存大小 */
      char _f[20-2*sizeof(long)-sizeof(int)]; 
      /* libc5的补丁
};
	//
    si_meminfo(&si);  
    // 限制可添加到epoll的最多的描述符数量  
    max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /  EP_ITEM_COST;  
    BUG_ON(max_user_watches < 0);  
    // 初始化递归检查队列  
    //
  ep_nested_calls_init(&poll_loop_ncalls);  
    ep_nested_calls_init(&poll_safewake_ncalls);  
    ep_nested_calls_init(&poll_readywalk_ncalls);  
    // epoll 使用的slab分配器分别用来分配epitem和eppoll_entry  
    epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),   0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);  
    pwq_cache = kmem_cache_create("eventpoll_pwq",    sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);  
    return 0;  
}  

epoll_wait()立刻返回就绪链表中的数据,执行epoll_ctl()的时候,如果增加句柄,检查红黑树是不是存在,如果存在返回,不存在则添加到树干之上.当作中断事件来准备就绪链表中插入数据

在这里我们简单说一下copy_to_usr_from 这个epoll中所调用的函数

static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
	if (access_ok(VERIFY_READ, from, n))
		n = __arch_copy_from_user(to, from, n);
	else /* security hole - plug it */
		memzero(to, n);
	return n;
}

*to是内核空间的指针,*from是用户空间指针,n表示从用户空间想内核空间拷贝数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数。

这个函数从结构上来分析,其实都可以分为两个部分:
首先检查用户空间的地址指针是否有效;
调用__arch_copy_from_user函数。

五.epoll的两种触发模式(LT和ET)

关于LT和ET的记忆可以参考,数字电路设计的电路库,LT是水平触发,ET是边缘触发

  • LT是水平触发模式,也就是在socket套接字的缓冲区中如果还有数据可读的话,epoll_wait会返回它的事件

  • ET(边缘触发)模式下,在它检测到有 I/O 事件时,通过 epoll_wait 调用会得到有事件通知的文件描述符,对于每一个被通知的文件描述符,如可读,则必须将该文件描述符一直读到空,让 errno 返回 EAGAIN 为止,否则下次的 epoll_wait 不会返回余下的数据,会丢掉事件。如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。

  • 在这里必须要提到的一点是ET只能和非阻塞的IO进行搭配使用,如果ET要搭配阻塞IO的话,那么将最后一次读的时候阻塞在那里.

有一个很有意思的东西,LT和ET其实只是一个if的区别,意料之外吧

  if (epi->event.events & EPOLLONESHOT)
                epi->event.events &= EP_PRIVATE_BITS;
            else if (!(epi->event.events & EPOLLET)) {
                /* 嘿嘿, EPOLLET和非ET的区别就在这一步之差呀~
                 * 如果是ET, epitem是不会再进入到readly list,
                 * 除非fd再次发生了状态改变, ep_poll_callback被调用.
                 * 如果是非ET, 不管你还有没有有效的事件或者数据,
                 * 都会被重新插入到ready list, 再下一次epoll_wait
                 * 时, 会立即返回, 并通知给用户空间. 当然如果这个
                 * 被监听的fds确实没事件也没数据了, epoll_wait会返回一个0,
                 * 空转一次.
                 */
                list_add_tail(&epi->rdllink, &ep->rdllist);
			}

六.反应堆模型

epoll_create(); // 创建监听红黑树
epoll_ctl(); // 向添加监听fd
epoll_wait(); // 监听
有客户端连接上来--->lfd调用acceptconn()--->将cfd挂载到红黑树上监听其读事件--->
epoll_wait()返回cfd--->cfd回调recvdata()--->将cfd摘下来监听写事件--->
epoll_wait()返回cfd--->cfd回调senddata()--->将cfd摘下来监听读事件--->...--->
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS 1024 /*监听上限*/
#define BUFLEN  4096    /*缓存区大小*/
#define SERV_PORT 4446  /*端口号*/

void recvdata(int fd,int events,void *arg);
void senddata(int fd,int events,void *arg);

/*描述就绪文件描述符的相关信息*/
struct myevent_s
{
    int fd;             //要监听的文件描述符
    int events;         //对应的监听事件,EPOLLIN和EPLLOUT
    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 的时间值
};

int g_efd;      //全局变量,作为红黑树根
struct myevent_s g_events[MAX_EVENTS+1];    //自定义结构体类型数组. +1-->listen fd
/*
 * 封装一个自定义事件,包括fd,这个fd的回调函数,还有一个额外的参数项
 * 注意:在封装这个事件的时候,为这个事件指明了回调函数,一般来说,一个fd只对一个特定的事件
 * 感兴趣,当这个事件发生的时候,就调用这个回调函数
 */
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int fd,int events,void *arg), void *arg)
{
    ev->fd = fd;
    ev->call_back = call_back;
    ev->events = 0;
    ev->arg = arg;
    ev->status = 0;
    if(ev->len <= 0)
    {
        memset(ev->buf, 0, sizeof(ev->buf));
        ev->len = 0;
    }
    ev->last_active = time(NULL); //调用eventset函数的时间
    return;
}

/* 向 epoll监听的红黑树 添加一个文件描述符 */
void eventadd(int efd, int events, struct myevent_s *ev)
{
    struct epoll_event epv={0, {0}};
    int op = 0;
    epv.data.ptr = ev; // ptr指向一个结构体(之前的epoll模型红黑树上挂载的是文件描述符cfd和lfd,现在是ptr指针)
    epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
    if(ev->status == 0)       //status 说明文件描述符是否在红黑树上 0不在,1 在
    {
        op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
        ev->status = 1;
    }
    if(epoll_ctl(efd, op, ev->fd, &epv) < 0) // 添加一个节点
        printf("event add failed [fd=%d],events[%d]\n", ev->fd, events);
    else
        printf("event add OK [fd=%d],events[%0X]\n", ev->fd, events);
    return;
}

/* 从epoll 监听的 红黑树中删除一个文件描述符*/
void eventdel(int efd,struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    if(ev->status != 1) //如果fd没有添加到监听树上,就不用删除,直接返回
        return;
    epv.data.ptr = NULL;
    ev->status = 0;
    epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
    return;
}

/*  当有文件描述符就绪, epoll返回, 调用该函数与客户端建立链接 */
void acceptconn(int lfd,int events,void *arg)
{
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    int cfd, i;
    if((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1)
    {
        if(errno != EAGAIN && errno != EINTR)
        {
            sleep(1);
        }
        printf("%s:accept,%s\n",__func__, strerror(errno));
        return;
    }
    do
    {
        for(i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素,类似于select中找值为-1的元素
        {
            if(g_events[i].status ==0)
                break;
        }
        if(i == MAX_EVENTS) // 超出连接数上限
        {
            printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
            break;
        }
        int flag = 0;
        if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
        {
            printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
            break;
        }
        eventset(&g_events[i], cfd, recvdata, &g_events[i]); //找到合适的节点之后,将其添加到监听树中,并监听读事件
        eventadd(g_efd, EPOLLIN, &g_events[i]);
    }while(0);
    printf("new connect[%s:%d],[time:%ld],pos[%d]",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
    return;
}
/*读取客户端发过来的数据的函数*/
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);    //读取客户端发过来的数据
    eventdel(g_efd, ev);                            //将该节点从红黑树上摘除
    if (len > 0) 
    {
        ev->len = len;
        ev->buf[len] = '\0';                        //手动添加字符串结束标记
        printf("C[%d]:%s\n", fd, ev->buf);                  
        eventset(ev, fd, senddata, ev);             //设置该fd对应的回调函数为senddata    
        eventadd(g_efd, EPOLLOUT, ev);              //将fd加入红黑树g_efd中,监听其写事件    
    } 
    else if (len == 0) 
    {
        close(ev->fd);
        /* ev-g_events 地址相减得到偏移元素位置 */
        printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
    } 
    else 
    {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
    }   
    return;
}
/*发送给客户端数据*/
void senddata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;
    len = send(fd, ev->buf, ev->len, 0);    //直接将数据回射给客户端
    eventdel(g_efd, ev);                    //从红黑树g_efd中移除
    if (len > 0) 
    {
        printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
        eventset(ev, fd, recvdata, ev);     //将该fd的回调函数改为recvdata
        eventadd(g_efd, EPOLLIN, ev);       //重新添加到红黑树上,设为监听读事件
    }
    else 
    {
        close(ev->fd);                      //关闭链接
        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }
    return ;
}

/*创建 socket, 初始化lfd */

void initlistensocket(int efd, short port)
{
    struct sockaddr_in sin;
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(lfd, F_SETFL, O_NONBLOCK);                //将socket设为非阻塞
    memset(&sin, 0, sizeof(sin));               //bzero(&sin, sizeof(sin))
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(port);
    bind(lfd,(struct sockaddr *)&sin, sizeof(sin));
    listen(lfd, 20);
    /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
    eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);    
    /* void eventadd(int efd, int events, struct myevent_s *ev) */
    eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);  //将lfd添加到监听树上,监听读事件
    return;
}
int main()
{
    int port=SERV_PORT;
    g_efd = epoll_create(MAX_EVENTS + 1); //创建红黑树,返回给全局 g_efd
    if(g_efd <= 0)
            printf("create efd in %s err %s\n", __func__, strerror(errno));    
    initlistensocket(g_efd, port); //初始化监听socket
    struct epoll_event events[MAX_EVENTS + 1];  //定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体
    printf("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_EVENTS+1, 1000);
        if (nfd < 0)
        {
            printf("epoll_wait error, exit\n");
            exit(-1);
        }
        for(i = 0; i < nfd; i++)
        {
		    //evtAdd()函数中,添加到监听树中监听事件的时候将myevents_t结构体类型给了ptr指针
            //这里epoll_wait返回的时候,同样会返回对应fd的myevents_t类型的指针
            struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
            //如果监听的是读事件,并返回的是读事件
            if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
            //如果监听的是写事件,并返回的是写事件
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
        }
    }
    return 0;
    }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值