epoll_create, epoll_ctl和epoll_w…

来源:互联网 发布:php经典书籍 编辑:程序博客网 时间:2024/06/11 22:09

Javascript 操作select控件大全(新增、修改、删除、选中、清空、判断存在等)

Posted on 2007-08-08 14:56 礼拜一 阅读(109930) 评论(42) 编辑 收藏

 epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么地方不明白或函数忘记了可以去看一下。 

epoll和select相比,最大不同在于: 


1epoll返回时已经明确的知道哪个sokcetfd发生了事件,不用再一个个比对。这样就提高了效率。 

2select的FD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。 


1、epoll_create函数 

函数声明:int epoll_create(int size) 

该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socketfd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socketfd数。随你定好了。只要你有空间。可参见上面与select之不同2. 


22、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的指针; 

如果调用成功返回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_data_t data;  

}; 



如: 

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,intmaxevents,int timeout) 

该函数用于轮询I/O事件的发生; 

参数: 

epfd:由epoll_create 生成的epoll专用的文件描述符; 

epoll_event:用于回传代处理事件的数组; 

maxevents:每次能处理的事件数; 

timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可 

返回发生事件数。 



用法如下: 


 

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 havadata 

...... 


epoll_wait运行的原理是 

等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokctfd和事件类型放入到events数组中。 

并 且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socketfd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socketfd的事件类型。这时不用EPOLL_CTL_ADD,因为socketfd并未清空,只是事件类型清空。这一步非常重要。 

俺最开始就是没有加这个,白搞了一个上午。 


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 insertion 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; 

         

          dosomething with the recved data........ 

      


在上面例子中没有说明对于listen socketfd该如何处理,有的时候会使用两个线程,一个用来监听accept另一个用来监听epoll_wait,如果是这样使用的话,则listensocketfd使用默认的阻塞方式就行了,而如果epoll_wait和accept处于一个线程中,即,全部由epoll_wait进行监听,则,需将listensocketfd也设置成非阻塞的,这样,对accept也应该使用while包起来(类似于上面的recv),因为,epoll_wait返回时只是说有连接到来了,并没有说有几个连接,而且在ET模式下epoll_wait不会再因为上一次的连接还没读完而返回,这种情况确实存在,我因为这个问题而耗费了一天多的时间,这里需要说明的是,每调用一次accept将从内核中的已连接队列中的队头读取一个连接,因为在并发访问的环境下,有可能有多个连接“同时”到达,而epoll_wait只返回了一次。


唯一有点麻烦是epoll有2种工作方式:LT和ET。 


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


ET (edge-triggered)是高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(onlyonce),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。


struct epoll_event { 
__uint32_t events;  
epoll_data_t data;  
}; 


如: 
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,intmaxevents,int timeout) 
该函数用于轮询I/O事件的发生; 
参数: 
epfd:由epoll_create生成的epoll专用的文件描述符; 
epoll_event:用于回传代处理事件的数组; 
maxevents:每次能处理的事件数; 
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可 
返回发生事件数。 


用法如下: 

 
struct epoll_event ev_read[20]; 
int nfds = 0; //return the eventscount 
nfds=epoll_wait(epoll_fd,ev_read,20,-1); 
for(i=0; i 

if(ev_read[i].data.fd == sock)// the listener port havadata 
...... 

epoll_wait运行的原理是 
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokctfd和事件类型放入到events数组中。 
并 且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socketfd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socketfd的事件类型。这时不用EPOLL_CTL_ADD,因为socketfd并未清空,只是事件类型清空。这一步非常重要。 
俺最开始就是没有加这个,白搞了一个上午。 

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 insertion 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 recveddata........ 
       

在上面例子中没有说明对于listen socketfd该如何处理,有的时候会使用两个线程,一个用来监听accept另一个用来监听epoll_wait,如果是这样使用的话,则listensocketfd使用默认的阻塞方式就行了,而如果epoll_wait和accept处于一个线程中,即,全部由epoll_wait进行监听,则,需将listensocketfd也设置成非阻塞的,这样,对accept也应该使用while包起来(类似于上面的recv),因为,epoll_wait返回时只是说有连接到来了,并没有说有几个连接,而且在ET模式下epoll_wait不会再因为上一次的连接还没读完而返回,这种情况确实存在,我因为这个问题而耗费了一天多的时间,这里需要说明的是,每调用一次accept将从内核中的已连接队列中的队头读取一个连接,因为在并发访问的环境下,有可能有多个连接“同时”到达,而epoll_wait只返回了一次。

唯一有点麻烦是epoll有2种工作方式:LT和ET。 

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

ET (edge-triggered)是高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(onlyonce),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
原创粉丝点击