IO多路复用之select,poll,epoll

news2024/11/15 15:31:37

所以,我们调用 select 会把所有要管理的 socket 的 fd (文件描述符,Linux下皆为文件,简单理解就是通过 fd 能找到这个 socket)传到内核中。

此时,要遍历所有 socket,看看是否有感兴趣的事件发生。如果没有一个 socket 有事件发生,那么 select 的线程就需要让出 cpu 阻塞等待,这个等待可以是不设置超时时间的死等,也可以是设置 timeout 的有超时时间的等待。

假设此时客户端发送了数据,网卡接收到的数据塞到对应的 socket 的接收队列中,此时 socket 知道来数据了,那如何唤醒 select 呢?

其实每个 socket 有个属于自己的睡眠队列,select 会安排一个内应,即在被管理的 socket 的睡眠队列里面塞入一个 entry。

当 socket 接收到网卡的数据后,就会去它的睡眠队列里遍历 entry,调用 entry 设置的 callback 方法,这个 callback 方法里就能唤醒 select !

所以 select 在每个被它管理的 socket 的睡眠队列里都塞入一个与它相关的 entry,这样不论哪个 socket 来数据了,它立马就能被唤醒然后干活!

但是,select 的实现不太好,因为唤醒的 select 此时只知道来活了,并不知道具体是哪个 socket 来数据了,所以只能傻傻地遍历所有 socket ,看看到底是哪个 scoket 来活了,然后把所有来活的 socket 封装成事件返回。

这样用户程序就能获得发生的事件,然后进行 I/O 和业务处理了。

这就是 select 的实现逻辑,理解起来应该不难。

这里再提一嘴 select 的限制,因为被管理的 socket fd 需要从用户空间拷贝到内核空间,为了控制拷贝的大小而做了限制,即每个 select 能拷贝的 fds 集合大小只有1024。

然后要改的话只能修改宏..再重新编译内核。网上很多文章都是这样说的,但是(没错有个但是)。

poll

poll 这玩意相比于 select 主要就是优化了 fds 的结构,不再是 bit 数组了,而是一个叫 pollfd 的玩意,反正就是不用管啥 1024 的限制了。

epoll(重点)

相信看了 select 的实现,我们稍微思考下,就能想出几个可以优化的点。

比如,为什么每次 select 需要把监控的 fds 传输到内核里?不能在内核里维护个?

为什么 socket 只唤醒 select,不能告诉它是哪个 socket 来数据了?

epoll 主要就是基于上面两点做了优化。

首先,搞了个叫 epoll_ctl 的方法,这方法就是用来管理维护 epoll 所监控的哪些 socket。

如果你的 epoll 要新加一个 socket 来管理,那就调用 epoll_ctl,要删除一个 socket 也调用 epoll_ctl,通过不同的入参来控制增删改。

epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);//添加

epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,&ev);//删除

这样,在内核里面就维护了此 epoll 管理的 socket 集合,这样就不用每次调用的时候都得把所有管理的 fds 拷贝到内核了。

对了,这个 socket 集合是用红黑树实现的。

然后和 select 类似,每个 socket 的睡眠队列里都会加个 entry,当每个 socket 来数据之后,同样也会调用 entry 对应的 callback。

与 select 不同的是,引入了一个 ready_list 双向链表,callback 里面会把当前的 socket 加入到 ready_list 然后唤醒 epoll。

这样被唤醒的 epoll 只需要遍历 ready_list 即可,这个链表里一定是有数据可读的 socket,相比于 select 就不会做无用的遍历了。

同时收集到的可读的 fd 按理是要拷贝到用户空间的,这里又做了个优化,利用了 mmp,让用户空间和内核空间映射到同一块内存中,这样就避免了拷贝。

  • poll_create是在内核区创建一个epoll相关的一些列结构,并且将一个句柄fd返回给用户态,后续的操作都是基于此fd的,参数size是告诉内核这个结构的元素的大小,类似于stl的vector动态数组,如果size不合适会涉及复制扩容,不过貌似4.1.2内核之后size已经没有太大用途了;
  • epoll_ctl是将fd添加/删除于epoll_create返回的epfd中,其中epoll_event是用户态和内核态交互的结构,定义了用户态关心的事件类型和触发时数据的载体epoll_data;
  • epoll_wait*是阻塞等待内核返回的可读写事件,epfd还是epoll_create的返回值,events是个结构体数组指针存储epoll_event,也就是将内核返回的待处理epoll_event结构都存储下来,maxevents告诉内核本次返回的最大fd数量,这个和events指向的数组是相关的;
  • epoll_wait是用户态需监控fd的代言人,后续用户程序对fd的操作都是基于此结构的;

ET&LT的区别:

ET:边沿触发。

按照上面的逻辑就是 epoll 遍历 ready_list 的时候,会把 socket 从 ready_list 里面移除,然后读取这个 scoket 的事件。

而 LT,水平触发,有点不一样。

在这个模式下 epoll 遍历 ready_list 的时候,会把 socket 从 ready_list 里面移除,然后读取这个 scoket 的事件,如果这个 socket 返回了感兴趣的事件,那么当前这个 socket 会再被加入到 ready_list 中,这样下次调用 epoll_wait 的时候,还能拿到这个 socket。

举个栗子:

如果此时一个客户端同时发来了 5 个数据包,按正常的逻辑,只需要唤醒一次 epoll ,把当前 socket 加一次到 ready_list 就行了,不需要加 5 次。然后用户程序可以把 socket 接收队列的所有数据包都读完。

但假设用户程序就读了一个包,然后处理报错了,后面不读了,那后面的 4 个包咋办?

如果是 ET 模式,就读不了了,因为没有把 socket 加入到 ready_list 的触发条件了。除非这个客户端发了新的数据包过来,这样才会再把当前 socket 加入到 ready_list,在新包过来之前,这 4 个数据包都不会被读到。而 LT 模式不一样,因为每次读完有感兴趣的事件发生之后,会把当前 socket 再加入到 ready_list,所以下次肯定能读到这个 socket,所以后面的 4 个数据包会被访问到,不论客户端是否发送新包。




#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<errno.h>
#include<pthread.h>
#include<sys/poll.h>
#include<sys/epoll.h>

#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
#define EVENTS_SIZE 1024

//线程
void *client_thread(void*arg){
	int clientfd=*(int*)arg;
	while(1){
		char buffer[BUFFER_LENGTH]={0};
		int buffer_size=recv(clientfd,buffer,BUFFER_LENGTH,0);
		printf("size: %d buffer:%s\n",buffer_size,buffer);
		send(clientfd,buffer,buffer_size,0);
	}
}

int main(){

	int sockfd=socket(AF_INET,SOCK_STREAM,0);//io

	//服务端
	struct sockaddr_in servaddr;
	servaddr.sin_family=AF_INET;//ipv4
	servaddr.sin_port=htons(9999);//端口  short
	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//0.0.0.0  long

	//bind(sockfd,sockaddr,sizeof(struct sockaddr)) 返回-1 bind失败
	//(struct sockaddr*)&servaddr   强转
	if(-1==bind(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))){
		printf("bind failed:%s\n",strerror(errno));
		return -1;
	}

	//监听
	listen(sockfd,10);
	printf("listen\n");

 	//睡眠5秒
	//sleep(5);

	//客户端
	struct sockaddr_in clientaddr;
	socklen_t len=sizeof(clientaddr);

#if 0

	while(1){

	
		//accept阻塞
		int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
		//printf("accept\n");

		char buffer[BUFFER_LENGTH]={0};
		int buffer_size=recv(clientfd,buffer,BUFFER_LENGTH,0);

		printf("size: %d buffer:%s\n",buffer_size,buffer);

		send(clientfd,buffer,buffer_size,0);

		
#endif	

//使用线程的方式
#if 0 
		pthread_t threadid;
		pthread_create(&threadid,NULL,client_thread,&clientfd);
	}

#endif

//IO多路复用-select
#if 0  // select, int fd
	
		//select(maxfd, rfds, wfds, efds, timeout);
		fd_set rfds, rset;
		FD_ZERO(&rfds);
		FD_SET(sockfd, &rfds);
	
		int maxfd = sockfd;
		int clientfd = 0;
	
		while (1) {  // master
	
			rset = rfds;
			
			int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
	
			if (FD_ISSET(sockfd, &rset)) { //
	
				clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
				printf("accept: %d\n", clientfd);
	
				FD_SET(clientfd, &rfds);
				if (clientfd > maxfd) maxfd = clientfd;
	
				if (-- nready == 0) continue;
			}
	
			int i = 0;
			for (i = sockfd+1; i <= maxfd;i ++) {
	
				if (FD_ISSET(i, &rset)) {
	
					char buffer[BUFFER_LENGTH] = {0};
					int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
					if (ret == 0) {
						close(clientfd);
						break; 
					}
	
					printf("ret: %d, buffer: %s\n", ret, buffer);
	
					send(clientfd, buffer, ret, 0);
				
				}	
			}		
		}
#endif

//IO多路复用-poll

#if 0
 
	struct pollfd fds[POLL_SIZE] = {0};

	
	fds[sockfd].fd = sockfd;
	fds[sockfd].events = POLLIN;

	int clientfd=0;
	int maxfd=sockfd;
	while(1){
		int nready=poll(fds,maxfd+1,-1);
		printf("hello\n");
		if(fds[sockfd].revents & POLLIN){
			
			clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
			printf("accept: %d\n", clientfd);

			fds[clientfd].fd=clientfd;
			fds[clientfd].events=POLLIN;
			if (clientfd > maxfd) maxfd = clientfd;

			if (-- nready == 0) continue;
		}

		int i=0;
		for(;i<=maxfd;i++){
			if(fds[i].revents & POLLIN){
				printf("i : %d\n",i);
				char buffer[BUFFER_LENGTH] = {0};
				int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
				if (ret == 0) {
					
					fds[clientfd].fd = -1;
					fds[clientfd].events = 0;
					close(clientfd);
					break; 
				}
				
				printf("ret: %d, buffer: %s\n",ret, buffer);
				
				send(clientfd, buffer, ret, 0);
			}

		}
	}


#endif	

//IO多路复用-epoll
#if 1
	//epoll
	int epfd=epoll_create(1);//参数为任意大于0的值,在一开始的版本中由于采用的为数组
								//所以参数为数组的大小,在后来的版本迭代中改用链表的数据结构
								//所以参数不再起作用,任意大于0的值即可

	struct epoll_event ev;
	ev.events=EPOLLIN;
	ev.data.fd=sockfd;

	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);

	struct epoll_event events[EVENTS_SIZE]={0};

	while(1){

		int nready=epoll_wait(epfd,events,EVENTS_SIZE,-1);//-1:一直等待,0:不等待,大于0:等待对于的单位时常
		if(nready<0)continue;

		int i=0;
		for(i=0;i<nready;i++){
			int connfd=events[i].data.fd;
			if(connfd==sockfd){
				int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
				if(clientfd<=0){
					continue;
				}
				printf("clientfd: %d\n",clientfd);
				ev.data.fd=clientfd;
				ev.events=EPOLLIN;
				epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);//添加
			}else if(events[i].events&EPOLLIN){
				char buffer[BUFFER_LENGTH]={0};
				int n=recv(connfd,buffer,BUFFER_LENGTH,0);
				if(n>0){
					printf("recv: %s\n",buffer);
					send(connfd, buffer, n, 0);
				}
				else if(n==0){
					printf("close\n");
					epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);//移除
					close(connfd);
				}
				
			}
		}
	}


#endif
	
}






本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/738681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux常见维护报错,修复MBR引导/修复grub2菜单/内核文件丢失

目录 一&#xff0c;修复MBR 1.模拟MBR损坏 2.重启查看系统 3.借助当前系统的光盘来进行修复 4.进入修复模式 进行修复 5.恢复正常 二&#xff0c;修复grub2菜单 1.出现情况 2.进入修复模式 3.进行修复grub2菜单 4.退出重启即可 三&#xff0c;内核文件丢失 1.进入安全模式 2.挂…

进销存软件哪个好?怎么选?(附模板)

已经把挑选进销存软件的几个注意点归纳的很到位了&#xff1a; 软件价格收费模式&#xff08;按年付费还是一次性买断&#xff09;功能要贴合本企业使用注重数据的准确性与安全性操作一定要简单要有良好的售后服务支持 在这几点的基础上&#xff0c;我简单说一下&#xff1a;…

运维开发面试题第四期(最后有数据库的题)

linux 如何查看当前linux系统的版本号&#xff08;uname -a 内核 cat /proc/version版本&#xff09;、系统状态《如CPU使用&#xff08;top - bn&#xff09;&#xff0c;内存使用情况&#xff08;vmstat,free-m&#xff09;》、如何查看监控磁盘 io负载&#xff08;iostat/ls…

Docker部署Mysql数据库详解

目录 1. Docker部署Mysql 1.1 Mysql容器 1.1.1 创建Mysql容器 1.1.2 进入Mysql容器并登录Mysql 1.1.3 持久化数据 1.2 远程登录Mysql 1.2.1 修改root加密方式 1.2.2 在容器启动时配置加密方式为mysql_native_password 1.3 Mysql编码 1.3.1 Mysql编码问题 1.3.2 Mysql编码…

虚函数是什么?虚函数是怎么实现的?什么时候用虚函数?

发现自己的笔记记录得四处都是&#xff0c;这些天统一整理汇总一下 1. 虚函数和多态的关系&#xff1a; 虚函数 往往 用于 实现 C的多态性 2. 什么是多态&#xff1a; &#xff08;1&#xff09;Wiki定义 &#xff1a; 指计算机程序运行时&#xff0c;相同的消息 发送给 多…

Acwing 848.有向图的拓扑序列

Acwing 848.有向图的拓扑序列 链接&#xff1a;848. 有向图的拓扑序列 - AcWing题库 /* 啥是拓扑排序? 首先要满足有向无环图的条件 一个有向图&#xff0c;如果图中有入度为 0 的点&#xff0c;就把这个点删掉&#xff0c;同时也删掉这个点所连的边。 一直进行上面出处理&am…

一个合格的测试人员需要具备迅速的反馈力

01 反馈指的是&#xff1a;在信息的传播中&#xff0c;接受者对传播者发出信息的反映。反馈得很重要一个属性就是时间滞延。在测试活动中&#xff0c;笔者经常会团队的测试人员一个问题&#xff1a;开发提交了一段代码后&#xff0c;多久能收到质量反馈&#xff1f;是按天&…

腾讯云轻量应用服务器性能测评一下,感觉还行吧

腾讯云轻量应用服务器性能如何&#xff1f;轻量服务器CPU内存带宽配置高&#xff0c;CPU采用什么型号主频多少&#xff1f;轻量应用服务器会不会比云服务器CVM性能差&#xff1f;腾讯云服务器网详解CPU型号主频、内存、公网带宽和系统盘存储多维对比&#xff0c;相对于CVM云服务…

Vue电商项目--登录与注册

登录注册静态组件 刚刚报了一个错误&#xff0c;找不到图片的资源 assets文件夹--放置全部组件共用静态资源 在样式当中也可以使用符号【src别名】。切记在前面加上 注册业务上 先修改原先的接口成这个按钮 然后把input框里面的数据保存到data中 注册业务下 就是点击获…

渠道归因(三)基于Shapley Value的渠道归因

渠道归因&#xff08;三&#xff09;基于Shapley Value的渠道归因 通过Shapley Value可以计算每个渠道的贡献权重&#xff0c;而且沙普利值的计算只需要参加的渠道总数&#xff0c;不考虑顺序&#xff0c;因此计算成本也较低。 传统的shapeley value import itertools from …

2.带你入门matlab数理统计常见分布的概率密度函数(matlab程序)

1.简述 计算概率分布律及密度函数值 matlab直接提供了通用的计算概率密度函数值的函数&#xff0c;它们是pdf 和namepdf函数&#xff0c;使用方式如下&#xff1a; Ypdf(‘name’&#xff0c;K&#xff0c;A&#xff0c;B)或者&#xff1a;namepdf (K&#xff0c;A&#xff0c;…

低代码平台之流程自动化测试

随着低代码平台的快速发展&#xff0c;开发人员可以便捷、快速地开发流程应用程序&#xff0c;由于业务流程的复杂化和业务需求的不断变化&#xff0c;对业务流程进行优化和改进将更加频繁&#xff0c;在这个过程中&#xff0c;就要求企业的流程测试的效率和质量需要跟上低代码…

Stable Diffusion - AWPortrait 1.1 模型与 Prompts 设置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131565908 AWPortrait 1.1 网址&#xff1a;https://www.liblibai.com/modelinfo/721fa2d298b262d7c08f0337ebfe58f8 介绍&#xff1a;AWPortrai…

java类的静态变量

java类的静态变量称为类变量&#xff0c;非静态变量称为实例变量。 静态变量可以在声明时初始化&#xff0c;也可以不在声明时初始化。 通过下面方式可以访问类的静态变量&#xff1a; 类内部直接访问静态变量&#xff1b;通过类名访问静态变量&#xff1b;通过实例访问静态变…

C++数据结构X篇_08_C++实现栈的顺序存储与链式存储

本篇参考C实现栈的顺序存储与链式存储整理&#xff0c;先搞懂结构框架&#xff0c;后期根据视频利用c对内容实现&#xff0c;也可以对c有更高的提升。 文章目录 1. 栈的顺序存储2. 栈的链式存储 栈是一种特殊的数据结构&#xff0c;栈中数据先进后出&#xff0c;且栈中数据只能…

30张图带你弄懂 二叉树、AVL、红黑树,他们之间有什么联系,AVL树和红黑树如何平衡

树&#xff08;Tree&#xff09;是若干个结点组成的有限集合&#xff0c;其中必须有一个结点是根结点&#xff0c;其余结点划分为若干个互不相交的集合&#xff0c;每一个集合还是一棵树&#xff0c;但被称为根的子树。注意&#xff0c;当树的结点个数为0时&#xff0c;我们称这…

[已解决]Running setup.py install for MinkowskiEngine ... error

虚拟环境中安装MinkowskiEngine&#xff1a; pip install -U MinkowskiEngine --install-option"--blasopenblas" -v --no-deps 报错&#xff1a;“Running setup.py install for MinkowskiEngine ... error” 解决办法[链接][参考1]&#xff1a; &#xff08;1&…

Unity 编辑器-创建模板脚本,并自动绑定属性,添加点击事件

当使用框架开发时&#xff0c;Prefab挂载的很多脚本都有固定的格式。从Unity的基础模板创建cs文件&#xff0c;再修改到应有的模板&#xff0c;会浪费一些时间。尤其是有大量的不同界面时&#xff0c;每个都改一遍&#xff0c;浪费时间不说&#xff0c;还有可能遗漏或错改。写个…

查询直播频道发起的签到记录

接口描述 1、通过直播场次id&#xff0c;查询签到发起记录 2、接口支持https协议 接口URL http://api.polyv.net/live/v3/channel/chat/checkin-by-sessionId 请求方式 GET 请求参数描述 参数名必选类型说明appIdtrueString账号appIdtimestamptrueLong当前13位毫秒级时间戳&…

OSPF实验2

OSPF实验2 要求&#xff1a; 1.如图连接&#xff0c;合理规划IP地址&#xff0c;所有路由器各自创建一个loopback接口 2.R1再创建三个接口IP地址为201.1.1.1/24、201.1.2.1/24、201.1.3.1/24 R5再创建三个接口IP地址为202.1.1.1/24、202.1.2.1/24、202.1.3.1/24 R7再创建三…