epoll

1、如果fd被注册到两个epoll中时,如果有事件发生则两个epoll都会触发事件。
2、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。
3、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。
4、epoll_wait会一直监听epollhup事件发生,所以其不需要添加到events中。
5、为了避免大数据量io时,et模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在下边轮询ready fd列表。
6、单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 所以你有还是必要建立线程池来发挥更大的效能。这点其实跟第5点是一个意思,线程池是个比较常用的解决方法。 
7、将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。 
8、epoll_ctl 与  epoll_wait不在一个线程行吗?,如果epoll_ctl加入了一个事件,epoll_wait能立即知道吗?还是需要退出来重新开始?可以,事件立即得到响应。

在水平触发模式下,如果对端socket关闭,则会一直触发epollin事件,驱动去处理client socket。
在边沿触发模式下,如果client首先发送协议然后shutdown写端。则会触发epollin事件。但是如果处理程序只进行一次recv操作时,根据recv收取到得数据长度来判读后边是否还有需要处理的协议时,将丢失客户端关闭事件。

 在nginx中,epoll_wait时间是最快发生的事件的时间,根据上面几点就不难理解了,可以很好的解决超时问题,而varnish中直接是永远等待,因为epoll作为单独线程加超时检测线程,效率也不错。

附带epoll_create中size的问题,之前就调查过,varnish中直接设置成1

 (0)int epoll_create (int  size) ;

(0.1)函数返回一个epoll专用的描述符epfd,epfd引用了一个新的epoll机制例程(instance.)。

(0.2)参数size是这个epoll专用描述符epfd所关联的socketfd的最大个数。man手册指出:The  size  is  not  the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures.  (Nowadays, size  is ignored; see NOTES below.)从2,6.8内核就不使用这个参数,而是内核动态分配所需的数据结构。

(0.3)当我们不再需要epfd时,一定要调用close关闭他。当epfd被关闭时,kernel销毁所引用的instance和释放相关资源。

正如man手册所指出的epoll_create的参数size,其实已经被抛弃了,毫无用处,但设置必须大于0。epoll_create1和epoll_create功效一样,而且还添加了一个flags参数,其值如下:

enum  {

    EPOLL_CLOEXEC = 02000000,//在新建的epfd上设置FD_CLOEXEC。

    EPOLL_NONBLOCK = 04000   //新建的epfd设置为非阻塞。

  };


成员events对应的值如下,他表示相应的描述符发生的事件或状态:

enum EPOLL_EVENTS

  {

    EPOLLIN = 0x001,//可读

    EPOLLPRI = 0x002,//有紧急数据可读,比如带外数据

    EPOLLOUT = 0x004,//可写

    EPOLLRDNORM = 0x040,

    EPOLLRDBAND = 0x080,

    EPOLLWRNORM = 0x100,

    EPOLLWRBAND = 0x200,

    EPOLLMSG = 0x400,

    EPOLLERR = 0x008,//出错

    EPOLLHUP = 0x010,//挂断

    EPOLLRDHUP = 0x2000,//连接断开,或处于半关闭状态(前提是对应的流socket,就是支持连接的socket)。man手册中的说明:(since Linux 2.6.17)  Stream socket peer closed connection, or shut down writing  half  of connection.  (This flag is especially useful for writing simple code to detect peer shutdown when using Edge Triggered monitoring.)

 

    EPOLLONESHOT = (1 << 30),/*默认监听一个socketfd之后并不把它从epfd关联的socketfd集合中删除,只清空socketfd对应的事件成员的值一次事件。EPOLLONESHOT表示设置socketfd为监听一次事件。当监听完这次事件之后,从epfd关联的socketfd集合中删除监听的socketfd,如果还需要继续监听这个socketfd的话,需要再次把这个socketfd加入到epfd关联的socketfd集合(队列)里 */

     EPOLLET = (1 << 31)// 将epfd设为边缘触发(Edge Triggered)模式,默认epfd是电平触发(Level Triggered)。 下文细述。

  }; 

对于epoll的网上一大抄的太多,而其中对ET模式的描述基本都是不准确的

以下是被抄袭最多的部分,而对于ET的误区就在这里

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

ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就 绪),内核不会发送更多的通知。

按照以上描述ET模式下,比如EPOLLIN事件到达,如果你不recv直到返回错误,那么之后有数据到达,系统不会再通知你。但事实上并不是这样,某些时候系统还是会通知你的,下面对Epoll事件触发时机做个总结

EPOLLIN

ET模式:

    每次EPOLL_CTL_ADD或EPOLL_CTL_MOD时,如果加入前,就是可读状态,那么加入后会触发1次 
    不管sock缓冲是否读完,只要对方有send或connect或cloase或强退,就会触发EPOLLIN 

LT模式:只要socket可读,就会一直触发EPOLLIN 

EPOLLOUT 
ET模式: 
    每次EPOLL_CTL_ADD或EPOLL_CTL_MOD时,如果加入前,就是可写状态,那么加入后会触发1次 
    不管sock发送缓冲是否从满变不满,只要对方有send或connect或cloase或强退,就会触发EPOLLOUT 

LT模式:只要socket可写,就会一直触发EPOLLOUT 


简单来说,ET模式下,只要监听了EPOLLIN和EPOLLOUT,socket的每次动作(包括close与不close直接强退),都会触发1次,   与读写缓冲的状态无关。  


EPOLLHUP 
    每次有未连接的socket被加入监听列队时,会触发EPOLLHUP 
    比如socket创建了套接字以后未调用connect或未调用listen,就直接加入epoll监听列队 
    或者close了以后再加入epoll监听列队 

#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main(int argc, char* argv[])
{
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen;


    if ( 2 == argc )
    {
        if( (portnumber = atoi(argv[1])) < 0 )
        {
            fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
        return 1;
    }

    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件

    struct epoll_event ev,events[20];
    //生成用于处理accept的epoll专用的文件描述符

    epfd=epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket设置为非阻塞方式

    //setnonblocking(listenfd);

    //设置与要处理的事件相关的文件描述符

    ev.data.fd=listenfd;
    //设置要处理的事件类型

    ev.events=EPOLLIN|EPOLLET;
    //ev.events=EPOLLIN;

    //注册epoll事件

    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr="127.0.0.1";
    inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);

    serveraddr.sin_port=htons(portnumber);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for ( ; ; ) {
        //等待epoll事件的发生

        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的所有事件

        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                //setnonblocking(connfd);

                char *str = inet_ntoa(clientaddr.sin_addr);
                cout << "accapt a connection from " << str << endl;
                //设置用于读操作的文件描述符

                ev.data.fd=connfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //ev.events=EPOLLIN;

                //注册ev
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
            {
                cout << "EPOLLIN" << endl;
                if ( (sockfd = events[i].data.fd) < 0)
                    continue;
                if ( (= read(sockfd, line, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    } else
                        std::cout<<"readline error"<<std::endl;
                } else if (== 0) {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                line[n] = '/0';
                cout << "read " << line << endl;
                //设置用于写操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的写操作事件

                ev.events=EPOLLOUT|EPOLLET;
                //修改sockfd上要处理的事件为EPOLLOUT
                //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
            else if(events[i].events&EPOLLOUT) // 如果有数据发送
            {
                sockfd = events[i].data.fd;
                write(sockfd, line, n);
                //设置用于读操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //修改sockfd上要处理的事件为EPOLIN
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
        }
    }
    return 0;
}

如果是 EPOLLOUT|EPOLLET 模式,对方一次调用 1000 次 send 操作. epoll_wait 返回后没有一次性把数据收完.

以后再次调用 epoll_ctrl 修改 EPOLLOUT|EPOLLET 事件, epoll_wait还是会返回.



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值