epoll_create, epoll_ctl和epoll_wait 讲解

NAME

       epoll - I/O event notification facility

SYNOPSIS
       #include 

DEscrīptION
       epoll is a variant of poll(2) that can be used either as Edge or Level
       Triggered interface and scales well to large numbers of watched fds.
       Three system calls are provided to set up and control an epoll set:
       epoll_create(2), epoll_ctl(2), epoll_wait(2).

      
An epoll set is connected to a file descrīptor created by
epoll_create(2).   Interest for certain file descrīptors is then
registered via
       epoll_ctl(2). Finally, the actual wait is started by epoll_wait(2).

其实,一切的申明都是多余的,遵守我今朝的懂得,EPOLL模型似乎只有一种格局,所以大师只要参考我下面的代码,就可以或许对EPOLL有所懂得了,代码的申明都已经在注释中:

while (TRUE)
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//守候EPOLL事务的产生,相当于监听,至于相干的端口,须要在初始化EPOLL的时辰绑定。
if (nfds <= 0)
   continue;
m_bOnTimeChecking = FALSE;
G_CurTime = time(NULL);
for (int i=0; i



其实EPOLL的精华,遵守我今朝的懂得,也就是上述的几段短短的代码,看来时代真的不合了,以前如何接管多量用户连接的题目,如今却被如此轻松的搞定,真是让人不得不感慨。

今天搞了一天的epoll,想做一个高并发的****代理法度。刚开端真是愁闷,一向搞不通,网上也有几篇介绍epoll的文章。但都不深切,没有将一些重视的处所疏解。以至于走了很多弯路,现将本身的一些懂得共享给大师,以少走弯路。

epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么处所不熟悉打听或函数忘怀了可以去看一下。
epoll和比拟,最大不合在于:

1epoll返回时已经明白的知道哪个sokcet fd产生了事务,不消再一个个比对。如许就进步了效力。
2的FD_SETSIZE是有限止的,而epoll是没有限止的只与体系资料有关。

1、epoll_create函数
函数声明:int epoll_create(int size)

函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想存眷的socket
fd上是否产生以及产生了什么事务。size就是你在这个epoll fd上能存眷的最大socket
fd数。随你定好了。只要你有空间。可拜见上方与之不合2.

2、epoll_ctl函数

函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于把握某个epoll文件描述符上的事务,可以注册事务,批改事务,删除事务。
参数:
  • epfd:由 epoll_create 生成的epoll专用的文件描述符;
  • op:要进行的操纵例如注册事务,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
  • fd:接洽关系的文件描述符;
  • event:指向epoll_event的指针;
  • return:若是调用成功返回0,不成功返回-1

用到的数据布局

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 */
};
如:
struct epoll_event ev;
//设置与要处理触发的事务相干的文件描述符
ev.data.fd=listenfd;
//设置要处理 触发的事务类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事务
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);


常用的事务类型:
EPOLLIN :默示对应的文件描述符可以读;
EPOLLOUT:默示对应的文件描述符可以写;
EPOLLPRI:默示对应的文件描述符有紧急的数据可读
EPOLLERR:默示对应的文件描述符产生错误;
EPOLLHUP:默示对应的文件描述符被挂断;
EPOLLET:默示对应的文件描述符有事务产生;



3、epoll_wait函数

函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事务的产生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理惩罚事务的数组;
maxevents:每次能处理惩罚的事务数;
timeout:守候I/O事务产生的超时价(单位我也不太清楚);-1相当于梗阻,0相当于非梗阻。一般用-1即可
return: 返回触发的事务数。


用法如下:

/*build the epoll enent for recall */
struct epoll_event ev_read[20];
int nfds = 0; //return the events count
nfds=epoll_wait(epoll_fd,ev_read,20, -1);
for(i=0; i
{
if(ev_read[i].data.fd == sock)// the listener port hava data
......


epoll_wait运行的道理是
等侍注册在epfd上的socket fd的事务的产生,若是产生则将产生的sokct fd和事务类型放入到events数组中。
并 且将注册在epfd上的socket fd的事务类型给清空

所以若是下一个轮回你还要存眷这个socket fd的话,则须要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来从头设置socket
fd的事务类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事务类型清空。这一步很是首要。
我最开端就是没有加这个,白搞了一个上午。

4 单个epoll并不克不及解决所有题目,希罕是你的每个操纵都斗劲费时的时辰,因为epoll是串行处理触发的。
所以你还是有须要建树线程池来阐扬更大的效能。

//
man中给出了epoll的用法,example法度如下:
       for(;;) {
           nfds = epoll_wait(kdpfd, events, maxevents, -1);

           for(n = 0; n < nfds; ++n) {
               if(events[n].data.fd == listener) {
                   client = accept(listener, (struct sockaddr *) &local,
                                   &addrlen);
                   if(client < 0){
                       perror("accept");
                       continue;
                   }
                   setnonblocking(client);
                   ev.events = EPOLLIN | EPOLLET;
                   ev.data.fd = client;
                   if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
                       fprintf(stderr, "epoll set ion error: fd=%d\n",
                               client);
                       return -1;
                   }
               }
               else
                   do_use_fd(events[n].data.fd);
           }
       }


此时应用的是ET模式,即,边沿触发,类似于电平触发,epoll中的边沿触发的意思是只对新到的数据进行通知,而内核缓冲区中若是是旧数据则不进行通知,所以在do_use_fd函数中应当应用如下轮回,才干将内核缓冲区中的数据读完。
        while (1) {
           len = recv(*******);
           if (len == -1) {
             if(errno == EAGAIN)
                break;
             perror("recv");
             break;
           }
           do something with the recved data........
        }

在 上方例子中没有申明对于listen socket fd该如何处理触发,有的时候会应用两个线程,一个用来监听accept另一个用来监听epoll_wait,若是是如许应用的话,则listen socket fd应用默认的梗阻体式格式就行了,而若是epoll_wait和accept处于一个线程中,即,全部由epoll_wait进行监听,则,需将
listen socket fd也设置成非梗阻的,如许,对accept也应当应用while包起来(类似于上方的recv),因为,epoll_wait返回时只是说有连接到来
了,并没有说有几个连接,并且在ET模式下epoll_wait不会再因为上一次的连接还没读完而返回,这种景象确切存在,我因为这个题目而花费了一天多
的时候,这里须要申明的是,每调用一次accept将从内核中的已连接队列中的队头读取一个连接,因为在并发接见的景象下,有可能有多个连接“同时”到
达,而epoll_wait只返回了一次。

独一有点麻烦是epoll有2种工作体式格式:LT和ET。

LT(level triggered)是缺省的工作体式格式,并且同时支撑block和no-block
socket.在这种做法中,内核告诉你一个文件描述符是否伏贴了,然后你可以对这个伏贴的fd进行IO操纵。若是你不作任何操纵,内核还是会持续通知你
的,所以,这种模式编程失足误可能性要小一点。传统的/poll都是这种模型的代表.

ET(edge-triggered)是高速工作体式格式,只支撑no-block
socket。在这种模式下,当描述符从未伏贴变为伏贴时,内核经由过程epoll告诉你。然后它会假设你知道文件描述符已经伏贴,并且不会再为那个文件描述
符发送更多的伏贴通知,直到你做了某些操纵导致那个文件描述符不再为伏贴状况了(比如,你在发送,接管或者接管恳求,或者发送接管的数据少于必然量时导致 了一个EWOULDBLOCK 错误)。然则请重视,若是一向不合错误这个fd作IO操纵(从而导致它再次变成未伏贴),内核不会发送更多的通知(only
once),不过在TCP和谈中,ET模式的加快效用仍须要更多的benchmark确认。

你可能感兴趣的:(Linux)