IO复用--epoll系统调用

epoll系统调用

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


这个文件描述符使用如下 epoll_create 函数来创建:

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

该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。


下面的函数用来操作epoll的内核事件表:

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

fd参数是要操作的文件描述符,op参数则指定操作类型,操作类型有如下3种:

  • EPOLL_CTL_ADD,往事件表中注册fd上的事件
  • EPOLL_CTL_MOD,修改fd上的注册事件
  • EPOLL_CTL_DEL,删除fd上的注册事件

event参数指定事件,它是 epoll_event 结构指针类型。epoll_event的定义如下:

struct epoll_event 
{
	__uint32_t   events;      /* Epoll事件 */用户关注的事件  例如EPOLLIN
	epoll_data_t data;        /* 用户数据 */
};

events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”,比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型——EPOLLET 和 EPOLLONESHOT。它们对于epoll的高效运作非常关键。

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

typedef union epoll_data 
{
	void  *ptr;
	int  fd;
	__uint32_t  u32;
	__uint64_t  u64;
} epoll_data_t;

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

epoll_ctl 成功时返回0,失败则返回 -1 并设置 errno。


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

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

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

timeout参数的含义与poll接口的timeout参数相同。

maxevents参数指定最多监听多少个事件,它必须大于0。

epoll_wait 函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中,这个数组只用于输出epoll_wait检测到的就绪事件。而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件,这就极大地提高了应用程序索引就绪文件描述符的效率


epoll linux独有 (将用户关注的文件描述符上的事件直接由内核记录)
一组函数:
int epoll_create(int size);//创建内核事件表(底层由红黑树维护)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //设置(添加、修改、删除)内核事件表中的文件描述符上的事件
op: EPOLL_CTL_ADD EPOLL_CTL_MOD CPOLL_CTL_DEL
int epoll_wait(int epollfd,struct epoll_event *revents,int maxevents,int timeout);
返回就绪文件描述符个数 revents:只返回所有的就绪文件描述符


epoll与poll对比

epoll不仅继承了poll相比于select的优势

  1. 文件描述符的范围
  2. 文件描述符的个数
  3. 事件类型更多

同时还解决了poll没解决的问题

  1. 用户关注的事件由内核维护,每次调用epoll_wait就不需要将用户空间的数据拷到内核空间。
  2. 每次epoll只会返回就绪的文件描述符
  3. 用户程序检测就绪文件描述符的时间复杂度位O(1)
  4. epoll内核实现比select和poll高效
    select poll 轮询方式
    epoll 回调方式
  5. epoll支持高效的ET模式,poll和select只能工作在低效的LT模式

epoll实现服务器与多客户端通信

	#define _GNU_SOURCE
	
	#include<stdio.h>
	#include<stdlib.h>
	#include<unistd.h>
	#include<assert.h>
	#include<string.h>
	
	#include<sys/socket.h>
	#include<netinet/in.h>
	#include<arpa/inet.h>
	#include<sys/types.h>
	
	#include<sys/epoll.h>
	
	#define SIZE 100


	int main()
	{
	    int sockfd=socket(AF_INET,SOCK_STREAM,0);
	    assert(sockfd!=-1);
	    
	    struct sockaddr_in ser,cli;
	    memset(&ser,0,sizeof(ser));
	    ser.sin_family=AF_INET;
	    ser.sin_port=htons(6000);
	    ser.sin_addr.s_addr=inet_addr("127.0.0.1");
	        
	    int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
	    assert(res!=-1);
	    
	    listen(sockfd,5);
	
	    int epollfd=epoll_create(5);
	    assert(epollfd!=-1);
	    
	    struct epoll_event event;
	    event.events=EPOLLIN;
	    event.data.fd=sockfd;
	
	    epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
	
	    while(1)
	    {
	        struct epoll_event events[SIZE];
	        int n=epoll_wait(epollfd,events,SIZE,-1);
	        if(n<=0)
	        {
	            printf("epoll wait error\n");    
	            continue;
	        }
	
	        int i=0;
	        for(;i<n;i++)
	        {
	            int fd=events[i].data.fd;
	            if(fd==sockfd)
	            {
	                int len=sizeof(cli);
	                int c=accept(fd,(struct sockaddr*)&cli,&len);
	                if(c<0)
	                {
	                    continue;    
	                }
	                event.events=EPOLLIN |EPOLLRDHUP;
	                event.data.fd=c;
	                epoll_ctl(epollfd,EPOLL_CTL_ADD,c,&event);
	                    
	            }
	            else if(events[i].events & EPOLLRDHUP)
	            {
	                epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
	                close(fd);
	                printf("%d was over\n",fd);
	                    
	            }
	            else if(events[i].events & EPOLLIN)
	            {
	                char buff[128]={0};
	                recv(fd,buff,127,0);
	                
	                printf("%d : %s\n",fd,buff);
	                
	                send(fd,"ok",2,0);
	                    
	            }
	        }
	        
	    }
	}

LT 和 ET模式

epoll 对文件描述符的操作有两种模式

  • LT (Level Trigger,电平触发)模式
  • ET (Edge Trigger,边沿触发)模式
    LT模式是默认的工作模式,这种模式下epoll 相当于一个效率较高的poll。

当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符ET模式是epoll的高效工作模式

对于采用LT工作模式的文件描述符,当 epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,当应用程序下一次调用epoll_wait时epoll_wait会再次向应用程序通告此事件,直到该事件被处理

而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait用将不再向应用程序通知这一事件,可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。

ET需要采用非阻塞描述符,LT阻塞与非阻塞描述符都可以。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值