linux系统编程——文件IO——事件轮询接口

1. 为什么需要epoll

select和poll的特点是:用户给内核一张 需要查看的文件描述符表,内核必须处理表中的每一个文件描述符,当这个表变大时,程序性能降低。

epoll将事件监视器的注册和事件监视工作分离,以避开这个问题。

2. api

      // @size : 用于提示内核要监控的文件描述符数目,并非最大数目,用于提升性能
       int epoll_create(int size);

       int epoll_create1(int flags);

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

           typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

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

           #define MAX_EVENTS 10
           struct epoll_event ev, events[MAX_EVENTS];
           int listen_sock, conn_sock, nfds, epollfd;

           /* Code to set up listening socket, 'listen_sock',
              (socket(), bind(), listen()) omitted */

           epollfd = epoll_create1(0);
           if (epollfd == -1) {
               perror("epoll_create1");
               exit(EXIT_FAILURE);
           }

           ev.events = EPOLLIN;
           ev.data.fd = listen_sock;
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
               perror("epoll_ctl: listen_sock");
               exit(EXIT_FAILURE);
           }

           for (;;) {
               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
               if (nfds == -1) {
                   perror("epoll_wait");
                   exit(EXIT_FAILURE);
               }

               for (n = 0; n < nfds; ++n) {
                   if (events[n].data.fd == listen_sock) {
                       conn_sock = accept(listen_sock,
                                          (struct sockaddr *) &addr, &addrlen);
                       if (conn_sock == -1) {
                           perror("accept");
                           exit(EXIT_FAILURE);
                       }
                       setnonblocking(conn_sock);
                       ev.events = EPOLLIN | EPOLLET;
                       ev.data.fd = conn_sock;
                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                                   &ev) == -1) {
                           perror("epoll_ctl: conn_sock");
                           exit(EXIT_FAILURE);
                       }
                   } else {
                       do_use_fd(events[n].data.fd);
                   }
               }
           }

3. 边缘触发和水平触发

若将epoll_ctl 的 event.events 设置为 EPOLLET,则使用边缘触发。
比如,如下事件

  1. 生产者将1KB数据写入管道
  2. 消费者对管道调用epoll_wait,等待管道送来数据并因此而读取。

若使用水平触发,则在调用2立即返回,表示读管道不会阻塞。
若使用边缘触发,则在调用1发生返回,说明 即使 管道已经可读,但epoll_wait也不会返回,除非再次发生写管道事件。

水平触发是默认行为,边缘触发需要使用不同的程序设计方法,通常用于非阻挡IO并且仔细检测EAGAIN.

上一篇:网络套接字(Udp与Tcp应用)


下一篇:Linux下安装MySQL(5.7)步骤