高性能服务器编程-----I/O多路复用(epoll)

对于poll我们也有很大的缺陷,比如我们依旧需要每次监听时从用户态将要监听的事件拷贝到内核态进行操作,并且poll与select都不能直接返回我们已就绪的文件描述符,而是需要用户进行循环判断事件是否就绪。。。Linux给我们提供了一个独有的I/O复用函数epoll,解决了这些问题。

epoll是Linux独有的,是select和poll的改进

epoll API详解

epoll不像poll和select只有单个函数,它是由一组函数来完成任务。epoll每次将用户直接将关心的事件放到内核里的一个事件表中,就不需要像select和poll那样每次调用都要重复传入用户定义的文件描述符集或事件集以拷贝给内核(省去了一次拷贝),epoll只需要使用一个额外的文件描述符来标识这个内核事件表。

创建内核事件表

#include<sys/epoll.h>
int epoll_create(int size);
        //返回的文件描述符标志内核事件表

size:这个参数不起什么作用,只是给内核一个提示,告诉它事件表需要多大,实际上epoll底层这个内核事件表就是一棵红黑树,是可以扩展的且查找效率高,所以该参数没有什么太大意义,你甚至可以传递0。

操作内核事件表

#incldue<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
                //成功返回0,失败返回-1并设置errno

epfd:指定要操作的内核事件表

op:指定操作的类型

EPOLL_CTL_ADD    往内核事件表中注册fd上关心的事件

EPOLL_CTL_MOD    在内核事件表中修改fd上的注册的事件

EPOLL_CTL_DEL      在内核时间表中删除fd上的注册的事件

fd:即要进行监听的文件描述符

event:指定关注事件类型

struct epoll_event
{
    __uint32_t events;   /*epoll事件*/
    epoll_data_t data;   /*用户数据*/
};

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

epoll_event中的events是用户所关注的fd上的事件类型,与poll的事件类型差不多,表示epoll的事件就在poll对应的宏前面加上"E"。但是epoll有两个额外的事件类型---EPOLLET和EPOLLONESHOT;对于epoll_event中的data常用于存储用户数据,它是一个epoll_data的联合体,使用最多的是fd,指定事件所从属的目标文件描述符,与epoll_ctl的第三个参数值往往是一样的。ptr成员往往用来指定fd相关的用户数据(联合体fd与ptr不能同时使用,可以在ptr指向的用户数据中包含fd)。epoll_data中的fd往往是用来epoll_wait中由内核填充使得用户可以获取到这个就绪的文件符的。

启动监听内核事件表

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
            //成功返回就绪文件描述符的个数,失败返回-1

timeout:与poll的参数意义一样,-1为阻塞直到有就绪事件发生,0是直接返回

maxevents:指定最多监听多少个事件(第二个参数数组大小),当达到maxevents时epoll_wait会返回

events:epoll_wait如果检测到事件,就将所有的就绪的事件从内核事件表中复制用户定义的这第二个参数events指向的数组中,这个数组只输出epoll_wait检测到的就绪事件,不用用户循环查找,提高了应用程序索引就绪文件描述符的效率O(1).

代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100
int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	struct sockaddr_in ser,cli;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(7700);
	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);   //创建内核事件表,5并不起作用,内核红黑树,可扩展
	assert(epollfd!=-1);

	struct epoll_event event; //将sockfd加入内核事件表
	event.events=EPOLLIN;  //sockfd的触发事件设为可读
	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++)   //n即就绪描述符的个数
		{
			int fd=events[i].data.fd;
			if(fd==sockfd)
			{
				int len=sizeof(cli);
				int c=accept(sockfd,(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)//同poll一样,先分析断开链接
			{
				epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
				close(fd);
				printf("%d:over\n",fd);
			}
			else if(events[i].events&EPOLLIN)
			{
				char buff[128]={0};
				int n=recv(fd,buff,127,0);
				if(n==-1)
				{
					printf("recv failed\n");
					continue;
				}
				printf("%d:%s\n",fd,buff);
				send(fd,"ok",2,0);
			}
			else 
				continue;
		}
	}
}

工作模式 

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)

LT模式是默认模式,LT模式与ET模式的区别如下:  

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。  

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

1. LT模式LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

2. ET模式ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值