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链接服务器,可以看到效果

posted @ 2011-12-30 13:25  bourneli  阅读(819)  评论(0编辑  收藏  举报