• 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);
    }
  • 相关阅读:
    Spring bean的循环依赖以及解决方式
    在Java中为什么实现了Cloneable接口,就能调用Object的clone方法?
    Java-Objects类-deepEquals()和equals()详解
    Linux TCP状态TIME_WAIT 过多的处理
    mysql字符串区分大小写的问题
    java 类加载
    java native 理解
    Maven配置阿里镜像仓库
    Cannot find name ‘XX‘. Do you need to change your target library? Try changing the `lib` compiler
    Typescript Interfaces(接口)添加任意key值/内容
  • 原文地址:https://www.cnblogs.com/tid-think/p/10779043.html
Copyright © 2020-2023  润新知