IO多路复用---epoll详解及示例

一、epoll系统调用
epoll是Linux特有的I/O复用函数。它的实现和使用上与select、poll有很大的差异。注意epoll是使用一组函数来完成任务的,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核的一个事件表里面,从而无需像select和poll那样每次调用都要重复传入文件描述符或事件集。

二、内核事件表

  1. 首先,epoll需要使用一个额外的文件描述符,来唯一表示内核中的这个事件表,这个文件描述符使用如下函数epoll_create函数来创建:
#include<sys/epoll.h>
int epoll_create(int size);
  • size参数并不起作用,只是给内核一个提示,告诉他事件表需要多大。该函数返回的文件描述符将作为其他epoll系统调用的第一个参数。以指定要访问的内核事件表。
  1. 通过epoll_ctl来操作内核事件表
#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
  • fd参数是要操作的文件描述符
  • op参数则指定操作类型,操作类型有一下三种:
EPOLL_CTL_ADD  //往事件表上注册fd的事件
EPOLL_CTL_MOD  //修改fd上的注册事件
EPOLL_CTL_DEL  //删除fd上的注册事件
  • event参数指定事件,它是一个epoll_event结构指针类型,epoll_event定义如下:
struct epoll_event
{
   
   _uint32_t events;   //epoll事件
   epolla_data_t data; //用户数据
}

其中event成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前面加上"E",比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型----EPOLLET和EPOLLONESHOT。data成员用于存储用户数据。

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

epoll_data_t是一个联合体,四个成员中用的最多的是fd,它指定事件所从属的目标的文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体,所以不能同时使用其ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,实现快速的数据访问,只能使用其他的手段,比如放弃epoll_data_t的fd成员,而在ptr指向的用户数据中包含fd。
epoll_ctl成功时返回0,失败返回-1并设置error。

三、epoll_wait函数

epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置error。

  • timeout参数的含义与poll接口的timeout参数相同。
  • maxevents参数指定最多监听多少个事件,该参数必须大于0;
  • epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,以下代码可以清楚的看到这个差别:
//如何索引poll返回的就绪文件描述符
int ret=poll(fds,MAX_EVENT_NUMBER,-1);
//必须遍历所有已注册文件描述符并找到其中的就绪者
for(int i=0;i<MAX_EVENT_NUMBET;++i)
{
   
     if(fds[i].revents &POLLIN)
     {
   
         int sockfd=fds[i].fd;
     }
}

//如何索引epoll返回的就绪文件描述符
int ret=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
//仅遍历就绪的ret个文件描述符
for(int i=0;i<ret;++i)
{
   
     int sockfd=events[i].data.fd;
     //sockfd肯定就绪,直接处理。
}

四、LT和ET模式
epoll对文件描述符的操作有两种模式:LT(水平触发)和 ET(边沿触发)模式。LT是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。

  • LT模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下次调用epoll_wait时,epoll_wait还会再次向应用程序通知此事件,直到该事件被处理。
  • ET模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。

可见,ET模式在很大程度上降低了同一个epoll事件呗重复触发的次数,因此效率要比LT模式高。
下面代码可以清除的体现LT和ET正在工作方式上面的差异:

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
//将文件描述符设置为非阻塞的
int setnonblocking(int fd)
{
   
    int old_option=fcntl(fd,F_GETFL);
    int new_option=old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
}
//将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中,
//参数enable_et指定是否对fd启用ET模式
void addfd(int epollfd,int fd,bool enable_et)
{
   
    epoll_event event;
    event.data.fd=fd;
    event.events=
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值