您的位置:新葡亰496net > 电脑系统 > 新葡亰496netepoll整理总计,libevent中epoll使用实例

新葡亰496netepoll整理总计,libevent中epoll使用实例

发布时间:2019-09-23 03:48编辑:电脑系统浏览(62)

    网络上所有资料都说epoll是高并发、单线程、IO重叠服用的首选架构,比select和poll性能都要好,特别是在有大量不活跃连接的情况下。具体原理就不阐述了,下面说说使用。

    server.h

    原地址:

    名词解释:man epoll之后,得到如下结果:

    Linux下select, poll和epoll IO模型的详解

    原文:

    一).Epoll 介绍

    Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的。其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TPC ( Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的 …

    二). 常用模型的缺点

    如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。

    ① PPC/TPC 模型

    这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

    ② select 模型

    1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …

    2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。

    3. 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

    总结为:1.连接数受限  2.查找配对速度慢 3.数据由内核拷贝到用户态

    ③ poll 模型

    基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。

    三). Epoll 的提升

    把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。

    ①. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。

    ②. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。

    ③. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

     四). Epoll 为什么高效

    Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。

    首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须轮询所有的 FD 集合,测试每个 FD 是否有事件发生,并处理事件;代码像下面这样:

     

    [cpp] view plain copy

     

    1. int res = select(maxfd 1, &readfds, NULL, NULL, 120);  
    2. if (res > 0)  
    3. {  
    4.     for (int i = 0; i < MAX_CONNECTION; i )  
    5.     {  
    6.         if (FD_ISSET(allConnection[i], &readfds))  
    7.         {  
    8.             handleEvent(allConnection[i]);  
    9.         }  
    10.     }  
    11. }  
    12. // if(res == 0) handle timeout, res < 0 handle error  

    Epoll 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD 集合。

     

     

    [cpp] view plain copy

     

    1. int res = epoll_wait(epfd, events, 20, 120);  
    2. for (int i = 0; i < res;i )  
    3. {  
    4.     handleEvent(events[n]);  
    5. }  

    五). Epoll 关键数据结构

     

    前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是:

     

    [cpp] view plain copy

     

    1. struct epoll_event {  
    2.     __uint32_t events;      // Epoll events  
    3.     epoll_data_t data;      // User data variable  
    4. };  
    5. typedef union epoll_data {  
    6.     void *ptr;  
    7.     int fd;  
    8.     __uint32_t u32;  
    9.     __uint64_t u64;  
    10. } epoll_data_t;  

    可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、指针等等。有了它,应用程序就可以直接定位目标了。

     

    六). 使用 Epoll

    既然 Epoll 相比 select 这么好,那么用起来如何呢?会不会很繁琐啊 … 先看看下面的三个函数吧,就知道 Epoll 的易用了。 

    int epoll_create(int size);  

    生成一个 Epoll 专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。

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

    控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏。

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

     

    等待 I/O 事件的发生;参数说明:

    epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;

    epoll_event: 用于回传代处理事件的数组;

    maxevents: 每次能处理的事件数;

    timeout: 等待 I/O 事件发生的超时值;

    返回发生事件数。

    相对于 select 模型中的 select 函数。

    七). 例子程序

    下面是一个简单 Echo Server 的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。

     

    [cpp] view plain copy

     

    1. //   
    2. // a simple echo server using epoll in linux  
    3. //   
    4. // 2009-11-05  
    5. // by sparkling  
    6. //   
    7. #include <sys/socket.h>  
    8. #include <sys/epoll.h>  
    9. #include <netinet/in.h>  
    10. #include <arpa/inet.h>  
    11. #include <fcntl.h>  
    12. #include <unistd.h>  
    13. #include <stdio.h>  
    14. #include <errno.h>  
    15. #include <iostream>  
    16. using namespace std;  
    17. #define MAX_EVENTS 500  
    18. struct myevent_s  
    19. {  
    20.     int fd;  
    21.     void (*call_back)(int fd, int events, void *arg);  
    22.     int events;  
    23.     void *arg;  
    24.     int status; // 1: in epoll wait list, 0 not in  
    25.     char buff[128]; // recv data buffer  
    26.     int len;  
    27.     long last_active; // last active time  
    28. };  
    29. // set event  
    30. void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)  
    31. {  
    32.     ev->fd = fd;  
    33.     ev->call_back = call_back;  
    34.     ev->events = 0;  
    35.     ev->arg = arg;  
    36.     ev->status = 0;  
    37.     ev->last_active = time(NULL);  
    38. }  
    39. // add/mod an event to epoll  
    40. void EventAdd(int epollFd, int events, myevent_s *ev)  
    41. {  
    42.     struct epoll_event epv = {0, {0}};  
    43.     int op;  
    44.     epv.data.ptr = ev;  
    45.     epv.events = ev->events = events;  
    46.     if(ev->status == 1){  
    47.         op = EPOLL_CTL_MOD;  
    48.     }  
    49.     else{  
    50.         op = EPOLL_CTL_ADD;  
    51.         ev->status = 1;  
    52.     }  
    53.     if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)  
    54.         printf("Event Add failed[fd=%d]/n", ev->fd);  
    55.     else  
    56.         printf("Event Add OK[fd=%d]/n", ev->fd);  
    57. }  
    58. // delete an event from epoll  
    59. void EventDel(int epollFd, myevent_s *ev)  
    60. {  
    61.     struct epoll_event epv = {0, {0}};  
    62.     if(ev->status != 1) return;  
    63.     epv.data.ptr = ev;  
    64.     ev->status = 0;  
    65.     epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);  
    66. }  
    67. int g_epollFd;  
    68. myevent_s g_Events[MAX_EVENTS 1]; // g_Events[MAX_EVENTS] is used by listen fd  
    69. void RecvData(int fd, int events, void *arg);  
    70. void SendData(int fd, int events, void *arg);  
    71. // accept new connections from clients  
    72. void AcceptConn(int fd, int events, void *arg)  
    73. {  
    74.     struct sockaddr_in sin;  
    75.     socklen_t len = sizeof(struct sockaddr_in);  
    76.     int nfd, i;  
    77.     // accept  
    78.     if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)  
    79.     {  
    80.         if(errno != EAGAIN && errno != EINTR)  
    81.         {  
    82.             printf("%s: bad accept", __func__);  
    83.         }  
    84.         return;  
    85.     }  
    86.     do  
    87.     {  
    88.         for(i = 0; i < MAX_EVENTS; i )  
    89.         {  
    90.             if(g_Events[i].status == 0)  
    91.             {  
    92.                 break;  
    93.             }  
    94.         }  
    95.         if(i == MAX_EVENTS)  
    96.         {  
    97.             printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);  
    98.             break;  
    99.         }  
    100.         // set nonblocking  
    101.         if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;  
    102.         // add a read event for receive data  
    103.         EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);  
    104.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, &g_Events[i]);  
    105.         printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);  
    106.     }while(0);  
    107. }  
    108. // receive data  
    109. void RecvData(int fd, int events, void *arg)  
    110. {  
    111.     struct myevent_s *ev = (struct myevent_s*)arg;  
    112.     int len;  
    113.     // receive data  
    114.     len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0);    
    115.     EventDel(g_epollFd, ev);  
    116.     if(len > 0)  
    117.     {  
    118.         ev->len = len;  
    119.         ev->buff[len] = '/0';  
    120.         printf("C[%d]:%s/n", fd, ev->buff);  
    121.         // change to send event  
    122.         EventSet(ev, fd, SendData, ev);  
    123.         EventAdd(g_epollFd, EPOLLOUT|EPOLLET, ev);  
    124.     }  
    125.     else if(len == 0)  
    126.     {  
    127.         close(ev->fd);  
    128.         printf("[fd=%d] closed gracefully./n", fd);  
    129.     }  
    130.     else  
    131.     {  
    132.         close(ev->fd);  
    133.         printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));  
    134.     }  
    135. }  
    136. // send data  
    137. void SendData(int fd, int events, void *arg)  
    138. {  
    139.     struct myevent_s *ev = (struct myevent_s*)arg;  
    140.     int len;  
    141.     // send data  
    142.     len = send(fd, ev->buff, ev->len, 0);  
    143.     ev->len = 0;  
    144.     EventDel(g_epollFd, ev);  
    145.     if(len > 0)  
    146.     {  
    147.         // change to receive event  
    148.         EventSet(ev, fd, RecvData, ev);  
    149.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);  
    150.     }  
    151.     else  
    152.     {  
    153.         close(ev->fd);  
    154.         printf("recv[fd=%d] error[%d]/n", fd, errno);  
    155.     }  
    156. }  
    157. void InitListenSocket(int epollFd, short port)  
    158. {  
    159.     int listenFd = socket(AF_INET, SOCK_STREAM, 0);  
    160.     fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking  
    161.     printf("server listen fd=%d/n", listenFd);  
    162.     EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);  
    163.     // add listen socket  
    164.     EventAdd(epollFd, EPOLLIN|EPOLLET, &g_Events[MAX_EVENTS]);  
    165.     // bind & listen  
    166.     sockaddr_in sin;  
    167.     bzero(&sin, sizeof(sin));  
    168.     sin.sin_family = AF_INET;  
    169.     sin.sin_addr.s_addr = INADDR_ANY;  
    170.     sin.sin_port = htons(port);  
    171.     bind(listenFd, (const sockaddr*)&sin, sizeof(sin));  
    172.     listen(listenFd, 5);  
    173. }  
    174. int main(int argc, char **argv)  
    175. {  
    176.     short port = 12345; // default port  
    177.     if(argc == 2){  
    178.         port = atoi(argv[1]);  
    179.     }  
    180.     // create epoll  
    181.     g_epollFd = epoll_create(MAX_EVENTS);  
    182.     if(g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);  
    183.     // create & bind listen socket, and add to epoll, set non-blocking  
    184.     InitListenSocket(g_epollFd, port);  
    185.     // event loop  
    186.     struct epoll_event events[MAX_EVENTS];  
    187.     printf("server running:port[%d]/n", port);  
    188.     int checkPos = 0;  
    189.     while(1){  
    190.         // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event  
    191.         long now = time(NULL);  
    192.         for(int i = 0; i < 100; i , checkPos ) // doesn't check listen fd  
    193.         {  
    194.             if(checkPos == MAX_EVENTS) checkPos = 0; // recycle  
    195.             if(g_Events[checkPos].status != 1) continue;  
    196.             long duration = now - g_Events[checkPos].last_active;  
    197.             if(duration >= 60) // 60s timeout  
    198.             {  
    199.                 close(g_Events[checkPos].fd);  
    200.                 printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);  
    201.                 EventDel(g_epollFd, &g_Events[checkPos]);  
    202.             }  
    203.         }  
    204.         // wait for events to happen  
    205.         int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);  
    206.         if(fds < 0){  
    207.             printf("epoll_wait error, exit/n");  
    208.             break;  
    209.         }  
    210.         for(int i = 0; i < fds; i ){  
    211.             myevent_s *ev = (struct myevent_s*)events[i].data.ptr;  
    212.             if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event  
    213.             {  
    214.                 ev->call_back(ev->fd, events[i].events, ev->arg);  
    215.             }  
    216.             if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event  
    217.             {  
    218.                 ev->call_back(ev->fd, events[i].events, ev->arg);  
    219.             }  
    220.         }  
    221.     }  
    222.     // free resource  
    223.     return 0;  
    224. }   

     

    3、epoll
    3.1、poll(select)的限制
          Poll函数起源于SVR3,最初局限于流设备,SVR4取消了这种限制。总是来说,poll比select要高效一些,但是,它有可移植性问题,例如,windows就只支持select。
    一个poll的简单例子:

    新葡亰496net 1代码

    新葡亰496net 2

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/poll.h>
    #define TIMEOUT 5       /* poll timeout, in seconds */
    int main (void)
    {
            struct pollfd fds[2];
            int ret;
            /* watch stdin for input */
            fds[0].fd = STDIN_FILENO;
            fds[0].events = POLLIN;
            /* watch stdout for ability to write (almost always true) */
            fds[1].fd = STDOUT_FILENO;
            fds[1].events = POLLOUT;
            /* All set, block! */
            ret = poll (fds, 2, TIMEOUT * 1000);
            if (ret == -1) {
                    perror ("poll");
                    return 1;
            }
            if (!ret) {
                    printf ("%d seconds elapsed.n", TIMEOUT);
                    return 0;
            }
            if (fds[0].revents & POLLIN)
                    printf ("stdin is readablen");
            if (fds[1].revents & POLLOUT)
                    printf ("stdout is writablen");
            return 0;
    }

    新葡亰496net 3

     

         select模型与此类例。内核必须遍历所有监视的描述符,而应用程序也必须遍历所有描述符,检查哪些描述符已经准备好。当描述符成百上千时,会变得非常低效——这是select(poll)模型低效的根源所在。考虑这些情况,2.6以后的内核都引进了epoll模型。

    3.2、核心数据结构与接口
    Epoll模型由3个函数构成,epoll_create、epoll_ctl和epoll_wait。
    3.2.1创建epoll实例(Creating a New Epoll Instance)
         epoll环境通过epoll_create函数创建:
         #include <sys/epoll.h>
          int epoll_create (int size)
          调用成功则返回与实例关联的文件描述符,该文件描述符与真实的文件没有任何关系,仅作为接下来调用的函数的句柄。size是给内核的一个提示,告诉内核将要监视的文件描述符的数量,它不是最大值;但是,传递合适的值能够提高系统性能。发生错误时,返回-1。
    例子:

    int epfd;
    epfd = epoll_create (100);  /* plan to watch ~100 fds */
    if (epfd < 0)
            perror ("epoll_create");

    3.2.2、控制epoll(Controlling Epoll)
    通过epoll_ctl,可以加入文件描述符到epoll环境或从epoll环境移除文件描述符。

    新葡亰496net 4代码

    新葡亰496net 5

    #include <sys/epoll.h>
    int epoll_ctl (int epfd,
                   int op,
                   int fd,
                   struct epoll_event *event);

    struct epoll_event {
            _ _u32 events;  /* events */
            union {
                    void *ptr;
                    int fd;
                    _ _u32 u32;
                    _ _u64 u64;
            } data;
    };

    新葡亰496net 6

     

     

     

    epfd为epoll_create返回的描述符。op表示对描述符fd采取的操作,取值如下:
    EPOLL_CTL_ADD
    Add a monitor on the file associated with the file descriptor fd to the epoll instance associated with epfd, per the events defined in event.

    EPOLL_CTL_DEL
    Remove a monitor on the file associated with the file descriptor fd from the epollinstance associated with epfd.
    EPOLL_CTL_MOD
    Modify an existing monitor of fd with the updated events specified by event.

    epoll_event结构中的events字段,表示对该文件描述符所关注的事件,它的取值如下:
    EPOLLET
    Enables edge-triggered behavior for the monitor of the file .The default behavior is level-
    triggered.
    EPOLLHUP
    A hangup occurred on the file. This event is always monitored, even if it’s not specified.
    EPOLLIN
    The file is available to be read from without blocking.
    EPOLLONESHOT
    After an event is generated and read, the file is automatically no longer monitored.A new event mask must be specified via EPOLL_CTL_MOD to reenable the watch.
    EPOLLOUT
    The file is available to be written to without blocking.
    EPOLLPRI
    There is urgent out-of-band data available to read.
    而epoll_event结构中的fd是epoll高效的根源所在,当描述符准备好。应用程序不用遍历所有描述符,而只用检查发生事件的描述符。
    将一个描述符加入epoll环境:

    新葡亰496net 7代码

    新葡亰496net 8

    struct epoll_event event;
    int ret;
    event.data.fd = fd; /* return the fd to us later */
    event.events = EPOLLIN | EPOLLOUT;
    ret = epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &event);
    if (ret)
            perror ("epoll_ctl");

    新葡亰496net 9

     

     

    3.2.3、等待事件(Waiting for Events with Epoll)

    #include <sys/epoll.h>
    int epoll_wait (int epfd,
                    struct epoll_event *events,
                    int maxevents,
                    int timeout);
    等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有 说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
    一个简单示例:

    新葡亰496net 10代码

    新葡亰496net 11

    #define MAX_EVENTS    64
    struct epoll_event *events;
    int nr_events, i, epfd;
    events = malloc (sizeof (struct epoll_event) * MAX_EVENTS);
    if (!events) {
            perror ("malloc");
            return 1;
    }
    nr_events = epoll_wait (epfd, events, MAX_EVENTS, -1);
    if (nr_events < 0) {
            perror ("epoll_wait");
            free (events);
            return 1;
    }
    //只需要检查发生事件的文件描述符,而不需要遍历所有描述符
    for (i = 0; i < nr_events; i ) {
            printf ("event=%ld on fd=%dn",
                    events[i].events,
                    events[i].data.fd);
            /*
             * We now can, per events[i].events, operate on
             * events[i].data.fd without blocking.
             */
    }
    free (events);

    新葡亰496net 12

     

    3.2.4、epoll的典型用法

    新葡亰496net 13代码

    新葡亰496net 14

    struct epoll_event ev, *events;
    for(;;) {
        nfds = epoll_wait(kdpfd, events, maxevents, -1);
        for(n = 0; n < nfds;  n) {
            if(events[n].data.fd == listener) {
                //新的连接
                client = accept(listener, (struct sockaddr *) &local,
                                &addrlen);
                if(client < 0){
                    perror("accept");
                    continue;
                }
                setnonblocking(client);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = client;
            // 设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面
                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
                    fprintf(stderr, "epoll set insertion error: fd=

    本文由新葡亰496net发布于电脑系统,转载请注明出处:新葡亰496netepoll整理总计,libevent中epoll使用实例

    关键词: