免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 906 | 回复: 0
打印 上一主题 下一主题

epoll模型的使用及其描述符耗尽问题的探讨 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-09-07 20:03 |只看该作者 |倒序浏览

                                转自:
http://bbs.chinaunix.net/viewthread.php?tid=813588
其中对epoll模型进行了较详细的讨论,这里记录一下主要内容
发表于: 2006-8-18 10:39               [引用]  [投诉] [快速回复]
每次接受新连接的时候,我监视了这几个事件。
EPOLLIN | EPOLLET |  EPOLLERR | EPOLLHUP | EPOLLPRI;
每次有一批事件返回,经过统计  
返回的一批fd数量=出错关闭的fd数量+由EPOLLIN转为EPOLLOUT的fd数量+EPOLLOUT正常处理关闭的fd的数量。 也就是说,每批事件都完全处理,没有遗漏。
观察发现EPOLLET |  EPOLLERR | EPOLLHUP 这3发事件的发生率为0。
但fd却成增大趋势。以前那写较小的fd在经历一段时间后渐渐丢失,不再可用。
请问fd都丢失到哪里去了?
=======================================================================================================
/*-------------------------------------------------------------------------------------------------
gcc -o httpd httpd.c -lpthread
author: wyezl
2006.4.28
---------------------------------------------------------------------------------------------------*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 8888
#define MAXFDS 5000
#define EVENTSIZE 100
#define BUFFER "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\nContent-Type: text/html\r\n\r\nHello"
int epfd;
void *serv_epoll(void *p);
void setnonblocking(int fd)
{
    int opts;
    opts=fcntl(fd, F_GETFL);
    if (opts  0)
    {
          setnonblocking(cfd);
          ev.data.fd = cfd;
          ev.events = EPOLLIN | EPOLLET |  EPOLLERR | EPOLLHUP | EPOLLPRI;
          epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
          //printf("connect from %s\n",inet_ntoa(cin.sin_addr));
          //printf("cfd=%d\n",cfd);
    }
    if (fd > 0)
          close(fd);
    return 0;
}
void *serv_epoll(void *p)
{
    int i, ret, cfd, nfds;;
    struct epoll_event ev,events[EVENTSIZE];
    char buffer[512];
    while (1)
    {
          nfds = epoll_wait(epfd, events, EVENTSIZE , -1);
          //printf("nfds ........... %d\n",nfds);
          for (i=0; i
#include
#include
#include
#include
#include
#include
#include
#include "fdevent.h"
#include "settings.h"
#include "buffer.h"
#ifdef USE_LINUX_EPOLL
static void fdevent_linux_sysepoll_free(fdevents *ev) {
        close(ev->epoll_fd);
        free(ev->epoll_events);
}
static int fdevent_linux_sysepoll_event_del(fdevents *ev, int fde_ndx, int fd) {
        struct epoll_event ep;
      
        if (fde_ndx epoll_fd, EPOLL_CTL_DEL, fd, &ep)) {
                fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
               
                SEGFAULT();
               
                return 0;
        }
      
      
        return -1;
}
static int fdevent_linux_sysepoll_event_add(fdevents *ev, int fde_ndx, int fd, int events) {
        struct epoll_event ep;
        int add = 0;
      
        if (fde_ndx == -1) add = 1;
      
        memset(&ep, 0, sizeof(ep));
      
        ep.events = 0;
      
        if (events & FDEVENT_IN)  ep.events |= EPOLLIN;
        if (events & FDEVENT_OUT) ep.events |= EPOLLOUT;
        /**
         *
         * with EPOLLET we don't get a FDEVENT_HUP
         * if the close is delay after everything has
         * sent.
         *
         */
      
        ep.events |= EPOLLERR | EPOLLHUP /* | EPOLLET */;
      
        ep.data.ptr = NULL;
        ep.data.fd = fd;
      
        if (0 != epoll_ctl(ev->epoll_fd, add ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ep)) {
                fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n", __FILE__, __LINE__, strerror(errno));
               
                SEGFAULT();
               
                return 0;
        }
      
        return fd;
}
static int fdevent_linux_sysepoll_poll(fdevents *ev, int timeout_ms) {
        return epoll_wait(ev->epoll_fd, ev->epoll_events, ev->maxfds, timeout_ms);
}
static int fdevent_linux_sysepoll_event_get_revent(fdevents *ev, size_t ndx) {
        int events = 0, e;
      
        e = ev->epoll_events[ndx].events;
        if (e & EPOLLIN) events |= FDEVENT_IN;
        if (e & EPOLLOUT) events |= FDEVENT_OUT;
        if (e & EPOLLERR) events |= FDEVENT_ERR;
        if (e & EPOLLHUP) events |= FDEVENT_HUP;
        if (e & EPOLLPRI) events |= FDEVENT_PRI;
      
        return e;
}
static int fdevent_linux_sysepoll_event_get_fd(fdevents *ev, size_t ndx) {
# if 0
        fprintf(stderr, "%s.%d: %d, %d\n", __FILE__, __LINE__, ndx, ev->epoll_events[ndx].data.fd);
# endif
      
        return ev->epoll_events[ndx].data.fd;
}
static int fdevent_linux_sysepoll_event_next_fdndx(fdevents *ev, int ndx) {
        size_t i;
      
        UNUSED(ev);
        i = (ndx type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
#define SET(x) \
        ev->x = fdevent_linux_sysepoll_##x;
      
        SET(free);
        SET(poll);
      
        SET(event_del);
        SET(event_add);
      
        SET(event_next_fdndx);
        SET(event_get_fd);
        SET(event_get_revent);
      
        if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) {
                fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
                        __FILE__, __LINE__, strerror(errno));
                return -1;
        }
        if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) {
                fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n",
                        __FILE__, __LINE__, strerror(errno));
                close(ev->epoll_fd);
                return -1;
        }
        ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events));
        return 0;
}
#else
int fdevent_linux_sysepoll_init(fdevents *ev) {
        UNUSED(ev);
        fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__);
      
        return -1;
}
#endif
========================================================================================================
EPOLLET 是边沿触发。如果epoll_wait返回一个可读的文件描述符,必须要把它缓冲区中的数据都读出来。如果没有读完,就继续下一次epoll_wait(),epoll就不会在这个描述符上被唤醒。
请参考MAN手册的建议
The suggested way to use epoll as an Edge Triggered (EPOLLET) interface is below, and possible pitfalls to avoid follow.
i   with non-blocking file descriptors
ii  by going to wait for an event only after read(2) or write(2) return EAGAIN
-------------------------------------------------
突然发现一个问题:
你使用了 EPOLLET(边沿触发)  和 非阻塞IO。
但是在接收数据时,你只接收一次,没有等到recv返回EAGAIN,就设置EPOLLOUT继续等待了。
假如这个描述符的缓冲区内还有数据没有读完,它就可能“死”在epoll里,不再被返回了。
我看到过一个人非常形象的描述这个问题:
  水平触发 --> 有事了,你不处理?不断骚扰你直到你处理。  边沿触发 --> 有事了,告诉你一次,你不处理?拉倒!
-------------------------------------------------------
我又按照下面步骤测试了一下
第一次:
1、epoll 在一个文件描述符上,等待一个EPOLLIN事件。
2、epoll 在这个文件描述符上被唤醒,然后只接收部分数据。(没有等到EAGAIN)
3、继续等待这个EPOLLIN事件。
4、epoll 没有再被唤醒。
正常现象。
第二次:
1、epoll 在一个文件描述符上,等待一个EPOLLIN事件。
2、epoll 在这个文件描述符上被唤醒,然后只接收部分数据。(没有等到EAGAIN)
3、继续等待这个EPOLLIN事件。
4、epoll 没有再被唤醒。
5、再次收到新数据后,epoll又在这个描述符上被唤醒了。
正常现象。
第三次:
1、epoll 在一个文件描述符上,等待一个EPOLLIN事件。
2、epoll 在这个文件描述符上被唤醒,然后只接收部分数据。(没有等到EAGAIN)
3、不再等待EPOLLIN,继续等待另一个EPOLLOUT事件。
4、epoll 在这个文件描述符上被唤醒了。
我怀疑EPOLLIN和EPOLLOUT是分别处理的。但是,也不能保证这种现象是正常行为。
第四次:
1、epoll 在一个文件描述符上,等待一个EPOLLOUT事件。
2、epoll 在这个文件描述符上被唤醒。
3、继续等待EPOLLOUT事件。
4、epoll 没有被唤醒。
5、收到新的数据,epoll_wait 在这个描述符上返回 EPOLLOUT 事件。
是否说明 IN 和 OUT 事件还是会相互影响呢?
=======================================================================================================
我认为原因是没有对每个FD进行超时的管理.
假设下列情况,一用户发起连接,服务器accept成功,返回了FD,但是在用户发起请求前,如果网络有问题,此FD当然无法返回POLLIN,当然就不会有POLLOUT等等了,这样FD就无法关闭.
KEEPALIVE可以解决此问题,这个不是指HTTP中的KEEPALIVE,是指socket选项.
个人观点,欢迎指教.
另,我个人认为,半连接,是不会占用FD的.如果只有半连接,我相信内核中的sock->ops->accept()函数是无法正确返回的.只有在这个函数正确返回的情况下,才会调用sock_map_fd(),把socket和fd关联起来.
-----------------------------------------------------------
我同意 solegoose 和 思一克 的说法。
其实,把这个问题的焦点集中在epoll上是不对的,我们有一点糊涂了。
当客户端的机器在发送“请求”前,就崩溃了(或者网络断掉了),则服务器一端是无从知晓的。
按照你现在的这个“请求响应方式”,无论是否使用epoll,都必须要做超时检查。
因此,这个问题与epoll无关。
===================================================================================================
在computer_xu的BLOG  http://blog.sina.com.cn/u/544465b0010000bp
中看到了如下翻译。也贴在这。
在man epoll中的Notes说到:
EPOLL事件分发系统可以运转在两种模式下:
   Edge Triggered (ET)
   Level Triggered (LT)
接下来说明ET, LT这两种事件分发机制的不同。我们假定一个环境:
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)......
Edge Triggered 工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
   i    基于非阻塞文件句柄
   ii   只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待
Level Triggered 工作模式
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
以上翻译自man epoll.
然后详细解释ET, LT:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
在许多测试中我们会看到如果没有大量的idle-connection或者dead-connection,epoll的效率并不会比 select/poll高很多,但是当我们遇到大量的idle-connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。
               
               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/1129/showart_376663.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP