• epoll用法【整理】


    l  epoll是什么?

    epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

    Linux下设计并发网络程序,常用的模型有:

    Ø  Apache模型(Process Per Connection,简称PPC

    Ø  TPCThread PerConnection)模型

    Ø  select模型和poll模型。

    Ø  epoll模型

    l  常用模型的缺点

    n  PPC/TPC模型

    这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

    n  select模型

    Ø  最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。

    Ø  效率问题select每次调用都会线性扫描全部fd集合,这样效率就会呈现线性下降,FD_SETSIZE改大可能造成这些fd都超时了。

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

    n  poll模型

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

    l  epoll的改进

    对比其他模型的问题,epoll的改进如下:

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

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

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

    l  epoll为什么高效

    epoll的高效和其数据结构的设计是密不可分的。

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

    int  res = select(maxfd+1, &readfds, NULL, NULL, 120);

    if (res > 0)

    {

        for (int i = 0; i < MAX_CONNECTION; i++)

        {

            if (FD_ISSET(allConnection[i],&readfds))

            {

                handleEvent(allConnection[i]);

            }

        }

    }

    // if(res == 0) handle timeout, res < 0 handle error

     

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

    int res = epoll_wait(epfd, events, 20, 120);

    for (int i = 0; i < res;i++)

    {

        handleEvent(events[n]);

    }

    l  epoll关键数据结构

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

    struct  epoll_event {

       __uint32_t      events;  //epoll events

       epoll_data_t   data;   //user data variable

    };

    typedef  union epoll_data {

       void*   ptr;

       int      fd;

       __uint32_t  u32;

       __uint64_t  u64;

    }epoll_data_t;

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

    l  使用epoll

    epollAPI:

    #include  <sys/epoll.h>

    int  epoll_create(int  size);

    int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

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

    Ø  int  epoll_create(int size);

    创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。

    Ø  int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

    epoll的事件注册函数。

    参数epfdepoll_create返回值。

    参数op

       EPOLL_CTL_ADD 注册新的fdepfd

       EPOLL_CTL_MOD 修改已经注册的fd的监听事件

       EPOLL_CTL_DEL epfd中删除一个fd

    参数fd是需要监听文件描述符。

    参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:

       EPOLLIN  可以读(包括对端Socket正常关闭)

       EPOLLOUT 可以写

       EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)

       EPOLLERR该文件描述符发生错误

       EPOLLHUP该文件描述符被挂断

       EPOLLET     epoll设置为边缘触发(Edge Triggered)模式。

       EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中

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

    等待事件的产生。

    参数events用来从内核得到事件的集合

    参数maxevents告之内核这个events有多大(maxevents不能大于size

    参数timeout是超时时间(毫秒)

    epoll的模式:

    Ø  LT模式:Level Triggered水平触发,

    这个是缺省的工作模式。同时支持block socketnon-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。

    Ø  ET模式:Edge Triggered 边缘触发

    是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到出错EAGAIN为止)。

    一个例子:

    #include <netdb.h>

    #include <sys/socket.h>

    #include <sys/epoll.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <fcntl.h>

    #include <unistd.h>

    #include <stdio.h>

    #include <string.h>

    #include <stdlib.h>

    #include <errno.h>

    /*创建并绑定一个socket作为服务器。 */

    staticint  create_and_bind (char *port){

        struct  addrinfo hints;

        struct  addrinfo *result, *rp;

        int  s, sfd;

        memset (&hints, 0, sizeof (struct addrinfo));

        hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */

        hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */

        hints.ai_flags = AI_PASSIVE;     /* All interfaces */

        s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址

        if (s != 0){

            fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));

            return -1;

        }

        for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可

            sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket

            if (sfd == -1)

                continue;

            s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket

            if (s == 0)

            {

                /* 绑定成功 */

                break;

            }

            close (sfd);

        }

        if (rp == NULL){

            fprintf (stderr, "Could not bind\n");

            return -1;

        }

        freeaddrinfo (result);

        return sfd;

    }

    /*

       设置socket为非阻塞模式。

       get flag,或上O_NONBLOCK set flag

     */

    static  int   make_socket_non_blocking (int sfd) {

        int flags, s;

        flags = fcntl (sfd, F_GETFL, 0);

        if (flags == -1){

            perror ("fcntl");

            return -1;

        }

        flags |= O_NONBLOCK;

        s = fcntl (sfd, F_SETFL, flags);

        if (s == -1){

            perror ("fcntl");

            return -1;

        }

        return 0;

    }

    #define  MAXEVENTS 64

    /*

       用法: ./epoll_test 8080

     */

    int  main (int argc, char *argv[]) {

        int sfd, s;

        int efd;

        struct  epoll_event event;

        struct  epoll_event *events;

        if (argc != 2) {

            fprintf (stderr, "Usage: %s [port]\n", argv[0]);

            exit (EXIT_FAILURE);

        }

        sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符

        s = make_socket_non_blocking (sfd);

        s = listen (sfd, SOMAXCONN);

        efd = epoll_create1 (0);

        event.data.fd = sfd;

        event.events = EPOLLIN | EPOLLET;

        s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);

        /* Buffer where events are returned,为events数组分配内存 */

        events = (struct  epoll_event*)calloc (MAXEVENTS, sizeof event);

        /* The event loop 事件循环*/

        while (1) {

            int n, i;

            n = epoll_wait (efd, events, MAXEVENTS, -1);

            for (i = 0; i < n; i++) {

                if ((events[i].events & EPOLLERR) ||  (events[i].events & EPOLLHUP) || (!(events[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 (events[i].data.fd);

                    continue;

                }elseif (sfd == events[i].data.fd) {//新连接

                  /* sfd上有数据可读,则表示有新连接

                   * We have a notification on the listening socket,

                   * which means one or more incoming connections. */

                    printf("Incoming connection !\n");

                    while (1) {

                        struct sockaddr in_addr;

                        socklen_t in_len;

                        int infd;

                        char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                        in_len = sizeof in_addr;

                        infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd

                        if (infd == -1) {

                            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {

                                /* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN=EWOULDBLOCK

                                 * We have processed all incoming connections. */

                                break;

                            } else  {

                                perror ("accept");

                                break;

                            }

                        }

                        s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);

                        if (s == 0) {

                            printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);

                        }

                        s = make_socket_non_blocking (infd);  //设置socket为非阻塞模式

                        event.data.fd = infd;  //data部分设置为fd

                        event.events = EPOLLIN | EPOLLET;  //监听EPOLLIN事件,使用边缘触发模式

                        s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);

                    }

                    continue;

                } else {//有客户端发来数据

                    /* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/

                    int done = 0;

                    while (1) {

                        ssize_t count;

                        char buf[512];

                        count = read (events[i].data.fd, buf, sizeof buf);

                        if (count == -1) {

                            if (errno != EAGAIN) { //如果errno=EAGAIN,表示我们已经读取了所有的数据

                                perror ("read");

                                done = 1;

                            }

                            break;

                        } elseif (count == 0) {  //读到文件尾(对端被关闭了)

                            /* End of file. The remote has closed the connection. */

                            done = 1;

                            break;

                        }

                        s = write (1, buf, count); /* 打印到屏幕 */

                    }

                    if (done) { //读完关闭(假设应用对每个连接只读取一次数据)

                        printf ("Closed connection on descriptor %d\n", events[i].data.fd);

                        /* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */

                        close (events[i].data.fd);

                    }

                }

            }

        }

        free (events);//释放内存

        close (sfd);   //关闭sfd

        return EXIT_SUCCESS;

    }

    主要参考链接:http://blog.csdn.net/ljx0305/article/details/4065058

  • 相关阅读:
    C# 根据URL获取网页截屏
    Django——WEB应用程序(手写程序),HTTP协议,BS CS架构
    jQuery——标签操作之(样式、文本内容、属性、文档处理)操作
    jQuery——简介,使用
    jQuery下载及应用
    javaScript——案例演示:点击有惊喜
    javaScript——案例演示:弹出模态框
    JavaScript——DOM操作+案例演示
    JavaScript——BOM操作
    JavaScript——杂碎小知识
  • 原文地址:https://www.cnblogs.com/apprentice89/p/3063039.html
Copyright © 2020-2023  润新知