epoll编程接口

一、概述

同I/O多路复用和信号驱动一样,Linux的epoll函数可以检查多个文件描述符上的I/O就绪状态,epoll函数的优点:

  1. 当检查大量文件描述符时,epoll的性能比select和poll函数高很多。

  2. epoll既支持水平触发也支持边缘触发,而select和poll支持水平触发,信号驱动只支持边缘触发。

  3. epoll性能和信号驱动I/O类似,但epoll可避免复杂的信号处理流程,可以指定检查的事件类型。

epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,epoll的核心数据结构称为epoll实例,它和一个打开的文件描述符相关联,这个文件描述符不是用来I/O操作的,是内核数据结构的句柄,这些内核数据结构实现的两个目的:

  1. 记录在进程中声明过的感兴趣的文件描述符列表(interest list-兴趣列表)。

  2. 维护了处于I/O就绪态的文件描述符列表(ready list-就绪列表)。

ready list中的成员是interest list的子集

对于由epoll检测的每一个文件描述符,我们可以指定一个位掩码来表示我们感兴趣的事件。这些位掩码同poll()所使用的位掩码有紧密的关联。

epoll API由下面3个系统调用组成:

  1. 系统调用epoll_create()创建一个epoll实例,返回代表该实例的文件描述符。

  2. 系统调用 epoll_ctl()操作同epol 实例相关联的兴趣列表。通过 epoll_ctl(),可以增加新的描述符到列表中,将已有的文件描述符从该列表中移除,以及修改代表文件描述符上事件类型的位掩码。

  3. 系统调用 epoll_wait()返回与 epoll 实例相关联的就绪列表中的成员。

二、epoll_create函数

此函数用于创建额为文件描述符表示内核中的事件表。

#include<sys/epoll.h>
int epoll_create(int size);

size参数并不起作用,只是给内核一个提示,告诉事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。

三、epoll_ctl函数

此函数用于操作epoll的内核事件表。

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_enent *event);
//fd:操作的文件描述符
//成功返回0,出错返回-1并设置errno

op指定操作类型,操作类型属性值如下:

  1. EPOLL_CTL_ADD:往事件表中注册fd上的事件。
  2. EPOLL_CTL_MOD:修改fd上的注册事件。
  3. EPOLL_CTL_DEL:删除fd上的注册事件。

event指定事件,指向epoll_evnet结构类型:

struct epoll_event{
   
	_uint32_t events;//epoll事件
	epoll_data data;//用户数据
};
  1. events成员描述事件类型,epoll支持的事件类型和poll基本相同。

  2. 表示epoll事件类型的宏是在poll对应的宏前加上"E",比如epoll的数据可读事件是EPOLLIN

  3. epoll有两个额外的事件类型一EPOLLET和EPOLLONESHOT,它们对于epoll的高效运作非常关键。

data成员用于存储用户数据,类型epoll_data_t的定义如下:

typedef union epoll_data{
   
	void *ptr;//指定与fd相关的用户数据。
	int	fd;//指定事件所从属的目标文件描述符。
	uint32_t u32;
	uint64_t u64;
	}epoll_data_t;//联合体

由于epoll_data_t是联合体,不能同时使用其ptr成员和fd成员,若需将文件描述符和用户数据关联起来,实现快速的数据访问,只能使用其他手段,如放弃fd成员,而在ptr指向的用户数据中包含fd。

通过max_user_watches文件查看文件描述符总数,避免上限。

四、epoll_wait函数

此函数作用是在一段超时时间内等待一组文件描述符上的事件。

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);
//maxevents:指定最多监听多少事件,必须大于0
//成功返回文件描述符个数,timeout超时间隔内没有任何文件描述符处于就绪态,返回0,出错返回-1设置errno。

timeout用来确定epoll_wait函数的阻塞行为:

  1. 若timeout 等于−1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生,或者直到捕获到一个信号为止。
  2. 若timeout 等于 0,执行一次非阻塞式的检查,看兴趣列表中的文件描述符上产生了哪个事件。
  3. 若timeout 大于 0,调用将阻塞至多 timeout 毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止。

五、poll和epoll使用上的区别

//如何索引poll返回的就绪文件描述符
int ret = poll(fds, MAX_EVENT_NUMBER, -1);
//必须遍历所有已注册文件描述符并找到其中的就绪者(当然,可以利用ret来稍做优化)
for(int i= 0; i < MAX_EVENT_NUMBER; ++i) 
{
   	
		if(fds[i].revents & POLLIN)//判断第i个文件描述符是否就绪
		{
   
			int sockfd = fds[i].fd;
			//处理sockfd
		}
}

//如何索引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模式

  1. LT(水平触发模式):如果文件描述符上可以非阻塞地执行I/O系统调用,此时认为它已经就绪。默认的工作模式阻塞和非阻塞都支持,相当于效率较高的poll。
  2. ET(边缘触发通知):如果文件描述符自上次状态检查以来有了新的I/O活动(新的输入),此时需要触发通知,只支持非阻塞。epoll的高效工作模式。

下表总结了I/O多路复用,信号驱动I/O以及epoll所采用的通知模型:
在这里插入图片描述
采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通知此事件,知道该事件被处理。

采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用不再向应用程序通知这一事件。

ET模式降低了同一个epoll事件被重复触发的次数,因此效率比LT模式高。

在这里插入图片描述

  1. 对于监听的 sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,可以用 while 来循环 accept()。

  2. 对于读写的 connfd,水平触发模式下,阻塞和非阻塞效果都一样,因为在阻塞模式下,如果数据读取不完全则返回继续触发,反之读取完则返回继续等待。全建议设置非阻塞。

  3. 对于读写的 connfd,边缘触发模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。

LT模式下,不需要读写的事件要及时移除,避免不必要的触发,浪费CPU资源;ET模式下,读写事件触发后,如果还需要得到为读写完的数据,就要及时再一次注册可读写事件

1.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 = EPOLLIN;
    if( enable_et )
    {
   
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

//LT模式的工作流程
void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
   
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
   
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
   
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
   
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
   
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
   
            printf( "something else happened \n" );
        }
    }
}

//ET模式的工作流程
void et( epoll_event* events, int number, int epollfd, int listenfd )
{
   
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
   
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
   
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值