linux编程:多路转接IO之epoll模型

linux编程:多路转接IO之epoll模型

epoll模型:linux下最好用的多路转接模型

  • epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
  • 另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
  • epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

操作流程

  1. 程序中创建epoll句柄。内核中对应struct eventpoll结构体(epol的各项描述符的监控信息都是存放在内核中的,不需要重复添加)
int epoll_create(int size);//size决定了所能监控的描述符数量上限,但是在linux内核2.6之后弃用,使用了动态增长
  1. 为每一个需要监控的描述符组织事件结构,添加到内核的eventpoll结构中(采用的数据结构是红黑树)
struct epollevent{
	uint32_t events;//需要监控的事件,以及监控返回后保存实际就绪的事件(EPOLLIN / EPOLLOUT)
	typedef union epoll_data{//这个信息是描述符就绪后返回的信息,通过这个信息决定就绪后对哪个描述符进行操作
		int fd; //通常会被设置为想要监控的描述符
		void *ptr; //有时候也会自己组织一个结构,将其地址赋值过来,结构中必须包含有操作的描述符
	}data;
}
int epoll ctl(int epfd--句柄,int cmd--操作类型, int fd--要监控的描述符,struct epollevent *ev--描述符对应的事件结构);
cmd: EPOLL_CTL_ADD / EPOLL_CTL_DEL/ EPOLL_CTL_MOD
  1. 调用epoll_wait开始监控,等到有描述符就绪或者等待超时则调用返回
int epoll_wait(int epfd,struct epollevent *evs,int maxevents,int timeout)
evs:是一个事件结构体数组的首地址,用于接收就绪的描述符事件结构信息
maxevents:每次想要获取的就绪事件最大数量,不大于数组的节点个数
  1. 监控返回后,根据epoll_wait返回值遍历evs数组,其中放置的都是就绪的描述符对应事件节点,直接对其中的描述符进行操作即可
    在这里插入图片描述

epoll的IO状态触发模式

1. 水平触发 EPOLLLT : epoll和select以及poll的默认触发方式,并且select和poll只有水平触发

  • 可读事件:接收缓冲区中有数据,就会触发就绪事件
  • 可写事件:发送缓冲区中有空闲空间,就会触发就绪事件

2. 边缘触发:EPOLLET

  • 可读事件:只有在新数据到来的时候才会触发一次事件(注意:若一次数据到来没有一次性读取完毕,则不会触发第二次)
  • 可写事件:只有缓冲区剩余空间从无到有的时候才会触发一次事件
  • 边缘触发,要求程序中最好能够一次将缓冲区中的数据完全读取,进行处理。但是因为我们不知道缓冲区中有多少数据,因此通常需要循环读取数据,但若是循环读取数据,若读取到缓冲区中没有数据,则recv会阻塞。
  • 解决方案:将套接字描述符的非阻塞属性开启(没有数据的时候立即报错返回–EAGAIN)
  • 设置方法:int fcntl(int fd, int cmd,…/* arg */); cmd: F_GETFL-获取属性,F_SETFL-设置属性–O_NONBLOCK(非阻塞属性)

边缘触发与水平触发的使用场景

  • 大多数的情况下都使用的是水平触发,只有在特定情况下会使用边缘触发,比如想要一次从缓冲区中读取到一条完整的数据
  • 边缘触发只是为了在特定情况下使用,避免水平触发一直触发就绪条件,但是不符合操作条件的空判断的情况,降低了程序处理效率

epoll的优缺点分析

  • 缺点:跨平台移植性差
  • 优点:
  1. 所能监控的描述符数量没有上限;
  2. 所有的描述符事件只需要向内核拷贝一次, 不需要重复拷贝
  3. 监控原理是异步阻塞操作(不是轮询遍历判断,而是监控由系统完成,系统为事件进行回调处理,将就绪事件添加到就绪链表中,程序只判断就绪事件链表是否为NULL就可以判断是否有就绪),性能不会随着描述符的增多而下降
  4. 直接返回的就是就绪的描述符以及事件信息,程序中进行遍历操作即可,没有空遍历

多路转接模型适用场景

  1. 有大量描述符需要进行事件监控:适用epoll
  2. 有单个描述符需要进行读写超时控制:适用select或poll
  3. 多路转接模型在大量描述符监控的情况下,只适用于有大量监控,但是同一时间只有少量描述符活跃的场景,因为多路转接模型,在流程中是对就绪的描述符进行轮询操作,如果活跃的描述符比较多,则最后一个描述符等待的时间会很长。
  4. 如果真的是大量活跃,这时候搭配线程池处理(将就绪的描述符放到线程池中,让线程池中的线程进行处理),多线程的均衡调度是系统负责。

epoll编程

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>//地址结构
#include<arpa/inet.h>//字节序转换接口
#include<sys/socket.h>//套接字接口
#include<sys/epoll.h>

#define MAX_LISTEN_NUM 5

int main()
{
	//1.创建套接字
	int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sockfd<0){
		perror("socket error");
		return -1;
	}
	//2.绑定地址信息
	struct sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_port=htons(9000);
	addr.sin_addr.s_addr=inet_addr("0.0.0.0");
	socklen_t len=sizeof(addr);
	int ret = bind(sockfd,(struct sockaddr*)&addr,len);
	if(ret<0){
		perror("bind error");
		return -1;
	}
	//3.开始监听
	ret =listen(sockfd,MAX_LISTEN_NUM);
	if(ret<0){
		perror("listen error");
		return -1;
	}
	int epfd=epoll_create(1);//创建epoll句柄
	struct epoll_event ev;//为每个需要监控的描述符组织事件节点信息
	ev.events=EPOLLIN;
	ev.data.fd=sockfd;
	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);//添加监控
	//4.获取新连接
	while(1){
		struct epoll_event evs[10]; //定义事件结构数组,同于获取就绪的事件节点
		int nfds=epoll_wait(epfd,evs,10,3000);//开始监控
		if(nfds<0){
			perror("epoll_wait error");
			continue;
		}else if(nfds==0){
			printf("No descriptor ready,timeout...\n");
			continue;
		}
		for(int i=0;i<nfds;i++){
			if(evs[i].events&EPOLLIN){//可读事件
				if(evs[i].data.fd==sockfd){//监听描述符的就绪事件
					struct sockaddr_in cliaddr;
					int newfd=accept(sockfd,(struct sockaddr*)&cliaddr,&len);
					if(newfd<0){
						perror("accept error");
						continue;
					}	
					struct epoll_event ev;
					ev.events=EPOLLIN;
					ev.data.fd=newfd;
					epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&ev);//将新建连接也添加进添加监控
				}else{
					//普通通信描述符的就绪事件
					char buf[1024]={0};
					//接收数据
					ret=recv(evs[i].data.fd,buf,1023,0);
					if(ret<=0){
						perror("recv error");
						epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,NULL);//移除监控
						close(evs[i].data.fd);
						continue;
					}
					printf("client say:%s\n",buf);
					//发送数据
					ret=send(evs[i].data.fd,buf,strlen(buf),0);
					if(ret<0){
						perror("send error");
						epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,NULL);
						close(evs[i].data.fd);
						continue;
					}
				}
			}else if(evs[i].events&EPOLLOUT){//可写事件	
			}
		}	
	}
	//6.关闭套接字
	close(sockfd);
	return 0;
}

通信演示
epoll_server
在这里插入图片描述
client客户端
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值