zoukankan      html  css  js  c++  java
  • epoll学习

    epoll全名event poll,他是poll的加强版本,从linux 2.6开始。

    select,poll,epoll的关系:

    • select,IO多路归并,也就是在单一线程中监控多个fd
    • poll:具有select的作用,但是select有个局限,被监听的fd数量有限,poll改进了这一点,并且相比于select而言,接口更方便
    • epoll:具有epoll的作用,但是poll是O(n)操作,即需要线性遍历所有的fd,逐一检测,而epoll进行了改进,通过事件注册,直接触发相关函数,不需要遍历所有注册的fd。

    epoll有三个接口:

    • epoll_create:创建epoll对象
    • epoll_ctl:控制epoll对象
    • epoll_wait:等待epoll事件发生

    这里一遍介绍epoll使用方法的文章 https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

    我将里面的元代码修改了,将一些方法提取出来,使得程序的整体脉络更清晰,记录在这里,以便参考:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/epoll.h>
    #include <errno.h>
    
    #define MAXEVENTS 64
    
    /**
     * 将socket设置为非堵塞
     * static 函数防止外部调用,默认都是extern
     */
    static int MakeSocketNonBlocking (int nSockFd);
    
    /**
     * 接受新的链接,并将其放到epoll中
     * @param nListenSock 监听socket
     * @param nEpoll epoll对象实例
     * @return 0 for OK,-1 for error
     */
    static int AcceptConnections(int nListenSock, int nEpoll);
    
    /**
     * 处理新链接的客户端
     */
    static int ProcessClient(int nClientSock);
    
    /**
     * 根据端口创建一个socket,绑定到指定端口并返回此socket。
     * 此方法兼容IPv4和IPv6
     */
    static int CreateAndBindSocket (char *szPort);
    
    /**
     * 主入口
     */
    int main (int argc, char *argv[])
    {
        if (argc != 2)
        {
            fprintf (stderr, "Usage: %s [port]\n", argv[0]);
            exit (EXIT_FAILURE);
        }
    
        int nListenSock = CreateAndBindSocket(argv[1]);
        if (nListenSock == -1)
        {
            abort();
        }
    
        int iRet = MakeSocketNonBlocking(nListenSock);
        if (iRet == -1)
        {
            abort();
        }
    
        iRet = listen(nListenSock, SOMAXCONN);
        if (iRet == -1)
        {
            perror ("listen");
            abort ();
        }
    
        // 创建epoll对象
        int nEpoll = epoll_create(1024);
        if (nEpoll == -1)
        {
            perror ("epoll_create");
            abort ();
        }
    
        struct epoll_event oEvent;
        oEvent.data.fd = nListenSock; // 此事件fd设置为监听socket
        oEvent.events = EPOLLIN | EPOLLET; // 注册读(in)事件和边界触发事件,默认为level-triggered
        iRet = epoll_ctl (nEpoll, EPOLL_CTL_ADD, nListenSock, &oEvent); // 将此事件添加到epoll对象中
        if (iRet == -1)
        {
            perror ("epoll_ctl");
            abort ();
        }
    
        /* Buffer where events are returned */
        struct epoll_event* pEventList = (epoll_event*)calloc(MAXEVENTS, sizeof oEvent);
    
        /* The event loop */
        while (1)
        {
            // 等待epoll事件发生,n为发生的个数,-1那么wait的事件有内核指定
            int n = epoll_wait(nEpoll, pEventList, MAXEVENTS, -1);
            for (int i = 0; i < n; i++)
            {
                if ((pEventList[i].events & EPOLLERR) ||
                    (pEventList[i].events & EPOLLHUP) ||
                    (!(pEventList[i].events & EPOLLIN)))
                {
                    /* An error has occured on this fd, or the socket is not
                    ready for reading (why were we notified then?) */
                    fprintf (stderr, "epoll error\n");
                    close (pEventList[i].data.fd);
                    continue;
                }
                else if (nListenSock == pEventList[i].data.fd) // 监听socket有in事件触发,说明有新的链接,那么添加到epoll中
                {
                    int iRet = AcceptConnections(nListenSock, nEpoll);
                    if (iRet == -1)
                    {
                        abort();
                    }
                }
                else // 处理所有的fd
                {
                    int iRet = ProcessClient(pEventList[i].data.fd);
                    if (iRet != 0)
                    {
                        abort();
                    }
                } // end of if
            } // end of for
        } // end of while
    
        free (pEventList);
        close (nListenSock);
    
        return EXIT_SUCCESS;
    }
    
    /**
     * 将socket设置为非堵塞
     * static 函数防止外部调用,默认都是extern
     */
    int MakeSocketNonBlocking (int nSockFd)
    {
        // 获取当前的flags
        int nFlags = fcntl (nSockFd, F_GETFL, 0);
        if (nFlags == -1)
        {
            perror ("fcntl");
            return -1;
        }
    
        // 添加O_NONBLOCK标记,也就是非堵塞
        nFlags |= O_NONBLOCK;
        int iRet = fcntl (nSockFd, F_SETFL, nFlags);
        if (iRet == -1)
        {
            perror ("fcntl");
            return -1;
        }
    
        return 0;
    }
    
    /**
     * 根据端口创建一个socket,绑定到指定端口并返回此socket。
     * 此方法兼容IPv4和IPv6
     */
    int CreateAndBindSocket (char *szPort)
    {
        // 传给函数getaddrinfo的提示数据结构,用于IPv4和IPv6兼容
        struct addrinfo oAddrHints;
        memset (&oAddrHints, 0, sizeof (struct addrinfo));
        oAddrHints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
        oAddrHints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
        oAddrHints.ai_flags = AI_PASSIVE;     /* All interfaces */
    
        // 获取当前host的信息数据
        struct addrinfo *pHostAddrInfo;
        int iRet = getaddrinfo (NULL, szPort, &oAddrHints, &pHostAddrInfo);
        if (iRet != 0)
        {
            fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (iRet));
            return -1;
        }
    
        int nSock = -1;
        struct addrinfo *pCurAddr; // 当前地址,host可以装有多个网卡,需要遍历每一个可用的ip
        for (pCurAddr = pHostAddrInfo; pCurAddr != NULL; pCurAddr = pCurAddr->ai_next)
        {
            nSock = socket (pCurAddr->ai_family, pCurAddr->ai_socktype, pCurAddr->ai_protocol);
            if (nSock == -1)
            {
                continue;
            }
            iRet = bind (nSock, pCurAddr->ai_addr, pCurAddr->ai_addrlen);
            if (iRet == 0)
            {
                /* We managed to bind successfully! */
                break;
            }
    
            close (nSock);
        }
    
        if (pCurAddr == NULL)
        {
            fprintf (stderr, "Could not bind\n");
            return -1;
        }
    
        // 释放内存
        freeaddrinfo (pHostAddrInfo);
    
        // 返回可以用的socket
        return nSock;
    }
    
    /**
     * 接受新的链接,并将其放到epoll中
     * @param nListenSock 监听socket
     * @param nEpoll epoll对象实例
     * @return 0 for OK,-1 for error
     */
    int AcceptConnections(int nListenSock, int nEpoll)
    {
        // We have a notification on the listening socket, which means one or more incoming connections.
        while (1)
        {
            struct sockaddr oClientAddr;
            socklen_t nSockLen = sizeof oClientAddr;
            int nConnSock = accept (nListenSock, &oClientAddr, &nSockLen);
            if (nConnSock == -1)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // We have processed all incoming connections.
                    break;
                }
                else
                {
                    perror ("accept");
                    return -1;
                }
            }
    
            // 输出新建连接的客户端地址
            char szHostBuf[NI_MAXHOST], szServerBuf[NI_MAXSERV];
            int iRet = getnameinfo( &oClientAddr, nSockLen,
                                szHostBuf, sizeof szHostBuf,
                                szServerBuf, sizeof szServerBuf,
                                NI_NUMERICHOST | NI_NUMERICSERV);
            if (iRet == 0)
            {
                printf("Accepted connection on descriptor %d "
                       "(host=%s, port=%s)\n", nConnSock, szHostBuf, szServerBuf);
            }
    
            /* Make the incoming socket non-blocking and add it to the
             list of fds to monitor. */
            iRet = MakeSocketNonBlocking (nConnSock);
            if (iRet == -1)
            {
                return -1;
            }
            struct epoll_event oEvent;
            oEvent.data.fd = nConnSock;
            oEvent.events = EPOLLIN | EPOLLET;
            iRet = epoll_ctl(nEpoll, EPOLL_CTL_ADD, nConnSock, &oEvent); // 将新的fd添加到epoll中
            if (iRet == -1)
            {
                perror ("epoll_ctl");
                return -1;
            }
        }
    
        return 0;
    }
    
    /**
     * 处理新链接的客户端
     */
    int ProcessClient(int nClientSock)
    {
        /* We have data on the fd waiting to be read. Read and
        display it. We must read whatever data is available
        completely, as we are running in edge-triggered mode
        and won't get a notification again for the same
        data. */
        int done = 0;
    
        while (1)
        {
           ssize_t count;
           char buf[512];
    
           count = read(nClientSock, buf, sizeof buf);
           if (count == -1)
           {
                /* If errno == EAGAIN, that means we have read all
                data. So go back to the main loop. */
                if (errno != EAGAIN)
                {
                   perror ("read");
                   done = 1;
                }
                break;
           }
           else if (count == 0)
           {
               /* End of file. The remote has closed the
                connection. */
               done = 1;
               break;
           }
    
            /* Write the buffer to standard output */
            int iRet = write (1, buf, count);
            if (iRet == -1)
            {
               perror ("write");
               return -1;
            }
        }
    
        if (done)
        {
            printf ("Closed connection on descriptor %d\n", nClientSock);
    
            /* Closing the descriptor will make epoll remove it
            from the set of descriptors which are monitored. */
            close (nClientSock);
        }
    
        return 0;
    }

    编译好后,使用telnet链接服务器,可以看到效果

  • 相关阅读:
    【刷题】LOJ 6038 「雅礼集训 2017 Day5」远行
    【刷题】BZOJ 4650 [Noi2016]优秀的拆分
    【刷题】BZOJ 4566 [Haoi2016]找相同字符
    【刷题】BZOJ 3238 [Ahoi2013]差异
    微信公众号_订阅号_被动回复用户消息
    艺术模板 art-template-web
    AJAX_违反了同源策略_就是"跨域"——jsonp 和 cors
    Ajax_简介: 异步的 JS 和 XML_原生写 ajax 分析其原理_jquery_ajax_art-template
    Node.js_express_临时会话对象 session
    BOM 浏览器对象模型_XMLHttpRequest 对象
  • 原文地址:https://www.cnblogs.com/bourneli/p/unp.html
Copyright © 2011-2022 走看看