linux epoll学习

以前针对多个fd进行数据(一般也就4、5个,没超过10个)读取,一般都是使用select操作,只有有任何一个fd有数据来了,都会返回,然后去匹配fd读取即可。

也可以设置阻塞时间,定时返回。

但是select最多只能支持1024个fd,而且效率不高,epoll却可以最大支持到65535个fd,而且效率更高。

更重要的是

epoll是线程安全的,不必用锁保护,更适合多线程操作。

EPOLL(7)                                               Linux Programmer's Manual                                              EPOLL(7)

NAME
       epoll - I/O event notification facility

SYNOPSIS
       #include <sys/epoll.h>

DESCRIPTION
       The  epoll  API  performs  a  similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of
       them.  The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of
       watched file descriptors.  The following system calls are provided to create and manage an epoll instance:

       *  epoll_create(2)  creates  an  epoll  instance  and  returns  a file descriptor referring to that instance.  (The more recent
          epoll_create1(2) extends the functionality of epoll_create(2).)

       *  Interest in particular file descriptors is then registered via epoll_ctl(2).  The set of file descriptors  currently  regis‐
          tered on an epoll instance is sometimes called an epoll set.

       *  epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.

   Level-triggered and edge-triggered
       The  epoll event distribution interface is able to behave both as edge-triggered (ET) and as level-triggered (LT).  The differ‐
       ence between the two mechanisms can be described as follows.  Suppose that this scenario happens:

       1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.

       2. A pipe writer writes 2 kB of data on the write side of the pipe.

       3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.

       4. The pipe reader reads 1 kB of data from rfd.

       5. A call to epoll_wait(2) is done.

       If the rfd file descriptor has been added to the  epoll  interface  using  the  EPOLLET  (edge-triggered)  flag,  the  call  to
       epoll_wait(2)  done  in  step 5 will probably hang despite the available data still present in the file input buffer; meanwhile
       the remote peer might be expecting a response based on the data it already sent.  The reason for this  is  that  edge-triggered
       mode  delivers  events only when changes occur on the monitored file descriptor.  So, in step 5 the caller might end up waiting
       for some data that is already present inside the input buffer.  In the above example, an event on rfd will be generated because
       of  the  write  done in 2 and the event is consumed in 3.  Since the read operation done in 4 does not consume the whole buffer
       data, the call to epoll_wait(2) done in step 5 might block indefinitely.

epoll也是一种io事件通知方式,有ET和LT两种工作模式,默认是LT模式,用户可以调用epoll_ctl方法通过EPOLL_CTL_MOD命令来改变工作模式。

LT(level triggered)

也叫做水平触发。

这种模式下,有事件发生促使epoll_wait唤醒后,但若未及时处理,下一次调用epoll_wait仍会继续通知。
在这种模式下有两种工作方式:阻塞和非阻塞。
自认为这两种工作方式并没有很明显的区别,因为只要被唤醒就一定有事件可读或者可写,阻塞模式下,一般情况并不会进入阻塞状态。在非阻塞模式下,会使用循环的方式进行读/写,直到完成或出现异常循环退出。

ET(edge trigger)
也叫边缘触发,与水平模式的区别是,调用epoll_wait通知过的事件,不论是否经过处理不会再次通知了,除非该fd下又有新的事件发生。

ET模式降低了同一个epoll事件被重复触发的次数,因此ET模式效率比LT模式高。

主要涉及到四个函数

#include <sys/epoll.h>

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
int close(int fd);
 
int epoll_create(int size);

第一个创建函数,创建一个epoll 的fd,size是指需要wait的最大fd数目。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第二个控制函数,用来控制fd的加入,移出,以及epoll工作模式的切换。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

第三个 wait函数,等待fd事件的响应,其中timeout参数是设置等待时长,以ms为单位。

如果timeout设置为-1则是阻塞到有事件产生才返回,返回值为事件总数,然后通过events里面的fd一个一个去匹配处理。

如果timeout不是-1,,则就算没有事件等待完timeout ms就会唤醒一次,返回值是0表示没有fd事件。

这个功能也可以当做一个定时器使用。

最后还有一个close。

下面配合一个服务器的sockFd来写个测试demo。

————————————————————————————————————————————————————————————————————————————————

第一步创建一个socket 和epoll ,把sock fd加入到epoll

//init socket
int Server::Init()
{
    socketFd = socket(PF_INET, SOCK_STREAM, 0);
    if (socketFd < 0) {
        perror("socketFd");
        return -1;
    }

    //ignore SIGPIPE
    signal(SIGPIPE, SIG_IGN);

    int ret = bind(socketFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    if (ret < 0) {
        perror("bind error");
        return -2;
    }

    if(ret < listen(socketFd, backLog)) {
        perror("listen error");
        return -3;
    }

    epFd = epoll_create(EPOLL_SIZE);
    if(epFd < 0) {
        perror("epFd error");
        return -4;
    }

    //add fd to epoll
    struct epoll_event ev;
    ev.data.fd = socketFd;
    ev.events = EPOLLIN | EPOLLHUP | EPOLLRDHUP;
    epoll_ctl(epFd, EPOLL_CTL_ADD, socketFd, &ev);

    return 0;
}

第二步开始epoll_wait

int Server::Start()
{
    struct epoll_event epEvents[EPOLL_SIZE] = {};
    int timeOut = -1;
    cout<<"start epoll wait..."<<endl;
    while (1)
    {
        //blocked
        int eventNum = epoll_wait(epFd, epEvents, EPOLL_SIZE, timeOut);
        if(eventNum < 0) {
            perror("epoll failure");
            return -1;
        }

        //handle epEvents
        for(int i = 0; i < eventNum; ++i) {
            int tmpFd = epEvents[i].data.fd;
            if ((epEvents[i].events & EPOLLERR) || (epEvents[i].events & EPOLLRDHUP)) {
                DeleteClient(tmpFd);
                if(tmpFd == socketFd) {
                    return -1;
                }
            }
            else if (epEvents[i].events & EPOLLIN) {
                if(tmpFd == socketFd) {//client connect
                    if (AcceptHandle() < 0) {
                        cerr<< strerror(errno)<<endl;
                        return -2;
                    }
                }
                else {  //client msg
                    if(ClientHandle(tmpFd) < 0) {
                        cerr<< strerror(errno)<<endl;
                    }
                }
            }
            else {
                cout<< "!!UNKNOWN events"<<endl;
            }
        }
    }
    return 0;
}

第三步,处理epoll Event事件

int Server::AcceptHandle()
{
    struct sockaddr_in client_address;
    socklen_t len = 0;

    int clientFd = accept(socketFd, ( struct sockaddr* )&client_address, &len);
    if (clientFd < 0) {
        return -1;
    }

    //add fd to epoll
    struct epoll_event ev;
    ev.data.fd = clientFd;
    ev.events = EPOLLIN | EPOLLHUP | EPOLLRDHUP;
    epoll_ctl(epFd, EPOLL_CTL_ADD, clientFd, &ev);
    return 0;
}

int Server::ClientHandle(int fd) {
    char buf[BUFSIZ] = {};

    int ret = recv(fd, buf, BUFSIZ, 0);
    if (ret < 0) {
        DeleteClient(fd);
        return -1;
    }

    cout<< buf<<endl;

    return 0;
}

void Server::DeleteClient(int fd)
{
    epoll_ctl(epFd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
}

最后用完了别忘了关闭

void Server::Close()
{
        close(epFd);
   
        close(socketFd);
}

猜你喜欢

转载自www.cnblogs.com/tid-think/p/10779043.html