Linux—I/O多路转接之epoll

一。什么是epoll

       按照man⼿册的说法:是为处理⼤批量句柄⽽作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel

2.5.44),它⼏乎具备了之前所说的⼀切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知⽅法


在了解epoll之前,我们要了解两个函数:

 select 和 poll 函数的功能:

  • select一次可以监测 FD_SETSIZE数量大小的描述符,FD_SETSIZE 通常是一个在 libc 编译时指定的小数字。
  • poll一次可以监测的描述符数量并没有限制,但撇开其它因素,我们每次都不得不检查就绪通知,线性扫描所有通过描述符,这样时间复杂度为 O(n)而且很慢。

epoll 没有这些固定限制,也不执行任何线性扫描。因此它可以更高效地执行和处理大量事件。

二。epoll的函数及其功能:

1. int epoll_create(int size);
创建⼀个epoll的句柄。⾃从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占⽤⼀个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使⽤完epoll后,必须调⽤close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,⽽是在这⾥先注册要监听的事件类型。
第⼀个参数是epoll_create()的返回值。
第⼆个参数表⽰动作,⽤三个宏来表⽰:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除⼀个fd;

第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事。struct epoll_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 */  
};
 

<span style="font-family:Microsoft YaHei;">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 */
};</span>

events可以是以下⼏个宏的集合:

EPOLLIN :表⽰对应的⽂件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表⽰对应的⽂件描述符可以写;

EPOLLPRI:表⽰对应的⽂件描述符有紧急的数据可读(这⾥应该表⽰有带外数据到来);

EPOLLERR:表⽰对应的⽂件描述符发⽣错误;

EPOLLHUP:表⽰对应的⽂件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于⽔平触发(LevelTriggered)来说的。

EPOLLONESHOT:只监听⼀次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加⼊到EPOLL队列⾥。

3.int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待在epoll监控的事件中已经发生的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发⽣的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在⽤户态中分配内存)。maxevents告之内核这个events有多⼤,这个 maxevents的值不能⼤于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会⽴即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调⽤成功,返回对应I/O上已准备好的⽂件描述符数⽬,如返回0表⽰已超时。


epoll的工作方式:

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET:边缘触发
,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。
下面举一个列子来说明LT和ET的区别(都是非阻塞模式,阻塞就不说了,效率太低):
采用LT模式下, 如果accept调用有返回就可以马上建立当前这个连接了,再epoll_wait等待下次通知,和select一样。
但是对于ET而言,如果accpet调用有返回,除了建立当前这个连接外,不能马上就epoll_wait还需要继续循环。


三。基于epoll的网络服务器实例代码:


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

#define EPOLL_REVS_SIZE 64

static void usage(const char *proc)
{
	printf("Usage: %s [local_ip] [local_port]\n",proc);
}
//typedef struct ep_buff{   //ET模式下需要的缓冲区  
//	                 //一个文件描述符对应一个缓冲区
//	int fd;
//	char buff[1024];
//}ep_buff_t,*ep_buff_p;

//
//void* alloc_ep_buff(int fd)
//{
//	ep_buff_p n = (ep_buff_p)malloc(sizeof(ep_buff_t));
//	if(!n){
//		perror("malloc");
//		exit(6);
//	}
//
//	n->fd = fd;
//	return n;
//}
//int set_fd_nonblaock() //ET模式下的函数   非阻塞
//{
//
//}
//int myread()
//{
//	while(1){}
//}
//
//int mywrite()
//{
//	while(1){
//	}
//}
//
//int myaccept(int epfd,int listen_sock)//获得一个新连接,就传进去
//{
//
//}


int startup(const char *ip,int port)
{
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0){
		perror("socket");
		exit(2);
	}
	int opt = 1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip);

	if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
		perror("bind");
		exit(3);
	}

	if(listen(sock,10) < 0){
		perror("listen");
		exit(4);
	}
	return sock;
}
int main( int argc, char *argv[])
{
	if(argc !=  3){
		usage(argv[0]);
		return 1;
	}
	int listen_sock = startup(argv[1],atoi(argv[2]));

	int epfd = epoll_create(256);
	if(epfd < 0){
		perror("epoll_create");
		return 5;
	}

	struct epoll_event ev;//创建需要监听的事件
	ev.events = EPOLLIN ;//| EPOLLET; //ET模式
	ev.data.fd = listen_sock;
	//ev.data.ptr = alloc_ep_buff(listen_sock);
	epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);//把listen_sock加入到epfd中

	int nums = -1;
	struct epoll_event revs[EPOLL_REVS_SIZE];//用来接收底层的事件数组
	int timeout = -1;
	while(1){
		switch((nums = epoll_wait(epfd,revs,\
			    EPOLL_REVS_SIZE,timeout))){
			case 0://代表超时
				perror("timeout...\n");
				break;
			case -1://代表函数出错
				perror("epoll_wait");
				break;
			default://此时至少有一个已经就绪
			{
				int i = 0;

       			for(;i < nums;i++){
				//int sock =((ep_buff_p)(revs[i].data.ptr))->fd;			                 			int sock = revs[i].data.fd;

				if(sock == listen_sock && \
				     (revs[i].events & EPOLLIN)){       //listen_sock ready!!!
					struct sockaddr_in client;
					socklen_t len = sizeof(client); 
							///创建新连接
					int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
					if(new_sock < 0){
		                     	perror("accept");
						continue;
					}
					printf("get client: [%s:%d]\n",\
					inet_ntoa(client.sin_addr),
					ntohs(client.sin_port));
			          	ev.data.fd = new_sock; //关注new_sock
					ev.events = EPOLLIN;  //此事件可以读
				      //ev.data.ptr = alloc_ep_buff(new_sock);
					//将new_sock加入到epfd中监听ev,也就是读事件
					epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);
				}else if(sock != listen_sock){           //如果不是listen_sock
				      if(revs[i].events & EPOLLIN){         //正常的fd 读事件就绪
						char buf[10240];
					   	ssize_t s = read(sock, buf,sizeof(buf)-1);
						//bug 如果一次不能读完的话,下次的数据会直接覆盖
						if(s > 0){				  			                    						printf("client: %s\n",buf);
			                        	ev.events = EPOLLOUT;
		       			      //ptr一直没有变,可以不用改
					      		//((ep_buff_p)(ev.data.ptr))->fd = sock;
		 			      		ev.data.fd = sock;
       			                    epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev);
						}else if(s <= 0){
					     		printf("client quit!!!!\n");
					     		epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
					     		close(sock);
					     		//free(revs[i].data.ptr);//ET
						}else{
					     		perror("read");
					     		epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
					    		close(sock);
					    		//free(revs[i].data.ptr);//ET
						}
					}else if(revs[i].events & EPOLLOUT){//写事件就绪
						const char *msg = "HTTP/1.0 OK 200\r\n\r\n<html><h1>hello epoll!</h1></html>";
						write(sock,msg,strlen(msg));//bug!!!			 							epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
						close(sock);
						//free(revs[i].data.ptr);//ET
					}else{}
					}
				}
			}								   	 
			break;
		}
	}
	return 0;
}

优缺点:http://blog.csdn.net/a1414345/article/details/73385556


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值