浅谈多路复用select、epoll

2023-10-31

一、多路复用技术,在理解多路复用之前了解一下IO阻塞、IO非阻塞有利于理解IO多路复用。可以想象成父进程为董事长,其雇佣秘书(内核)帮助你监听读写缓冲区,常见的有select、poll、epoll,这里只谈一下select和epoll,poll不常用。

二、高并发情况下,活跃量高选用select,活跃量低选epoll。

select:Windows使用较多,跨平台。

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)

FD_CLR、FD-ISSET、FD_SET、FD_ZERO搜索引擎了解。

epoll有两种模式:水平触发(LT)、边沿触发(ET)。水平触发时,假设你设定读buf为5个字节,读缓冲区有50个字节数据,就需要读10次;写时,只要写缓冲区有空间就会一直写。边沿触发:只触发一次,只有当读写缓冲区容量发生变化时才会再次触发。

epoll步骤:1.epoll_create:创建树(红黑树,内核帮助你维护);2.epoll_ctl:上树(在监听阶段遇到错误需关闭socket套接字(TCP),下树时将epoll_ctl的option设为EPOLL_CTL_DEL);3.epoll_wait:监听,返回的是状态变化fd的个数。

select和epoll的优缺点,百度、牛客都有。

代码示例:

select:

#include<stdio.h>
#include<sys/time.h>
#include<sys/select.h>
#include<sys/type.h>
#include<unistd.h>
#include"wrap.h"

#define PORT 8888

int main()
{
	//创建套接字、绑定
	int lfd = tcp4bind(PORT,NULL);
	//监听
	Listen(lfd,128);
	int maxfd = lfd;//最大的文件描述符
	fd_set oldset,rset;
	FD_ZERO(&oldset);//清空
	FD_ZERO(&rset);
	//将fd添加到oldset中
	FD_SET(lfd,&oldset);
	//while
	while(1)
	{
		//select监听,每次都需要将oldset赋值给需要监听的rset,rset中只存发生状态变化的文件描述符
		rset = oldset;
		//只监听读状态的文件描述符,不需要监听写和异常,也不需要timeout
		//n为状态变化文件描述符个数
		int n = select(maxfd + 1,&rset,NULL,NULL,NULL);
		if(n < 0)
		{
			perror("");
			break;
		}
		else if(n == 0)
		{
			continue;
		}
		else//n>0,有n个文件描述符状态发生了变化
		{
			//监听到了文件描述符的变化
			//lfd变化
			if(FD_ISSET(lfd,&rset))
			{
				//有新的连接到来需要将其添加至oldset中
				struct sockaddr_in cliaddr;
				socklen_t len = sizeof(cliaddr);
				char ip[16] = "";
				//提取新的连接
				int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
				//打印新连接的ip和端口
				printf("new client ip = %s port = %d\n"
				,inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16)
				,ntohs(cliaddr.sin_port));
				//将新来的lfd添加至oldset以下次监听
				FD_SET(cfd,&oldset);
				//判断新来的fd与之前的maxfd谁更大,如果更大,更新
				if(cfd > maxfd)
				{
					maxfd = cfd;
				}
				//判断是否只有lfd变化,使用n,n为变化的文件描述符个数
				if(--n == 0)
				{
					continue;
				}
			}
			//遍历cfd,判断lfd之后的文件描述符是否在rset中
			for(int i = lfd + 1;i<maxfd;i++)
			{
				if(FD_ISSET(i,&rset))
				{
					char buf[1500] = "";
					int ret = Read(i,buf,sizeof(buf));
					if(ret < 0)
					{
						//出错,需要将此cfd关闭,并从oldset中删除
						perror("");
						close(i);//关闭
						FD_CLR(i,&oldset);
						continue;
					}
					else if(ret == 0)
					{
						printf("client close!\n");
						close(i);
						FD_CLR(i,&oldset);
					}
					else
					{
						printf("%s\n",buf);
						Write(i,buf,ret);
					}
				}
				
			}
		}
	}
	return 0;
}

epoll:

//父、子进程使用pipe,父进程使用epoll监听
#include<stdio.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>
#include"wrap.h"

int main()
{
	//父、子进程之间,父进程写,子进程读,使用管道pipe用于线程之间通信,父进程使用epoll监听
	int fd[2];
	pipe(fd);
	//创建子进程
	pid_t pid;
	pid = fork();
	if(pid < 0)
	{
		perror("");
	}
	else if(pid == 0)
	{
		close(fd[0]);//子进程只写
		char buf[5] = "";
		char ch = 'a';
		while(1)
		{
			sleep(1);
			memset(buf,ch++,sizeof(buf));
			write(fd[1],but,5);
		}
	}
	else
	{
		close(fd[1]);//父进程只读
		//创建树
		int epfd = epoll_create(1);
		//上树
		struct epoll_event ev,evs[1];
		ev.data.fd = fd[0];
		ev.events = EPOLLIN;//只读
		//上树
		epoll_ctl(epfd,EPOLL_CTL_ADD,fd[0],&ev);
		//监听
		while(1)
		{
			int n = epoll_wait(epfd,evs,1,-1);//返回的是状态变化的个数
			if(n == 1)
			{
				char buf[128] = "";
				int ret = read(fd[0],buf,sizeof(buf));
				if(ret <= 0)
				{
					//出错,关闭fd,并且下数
					close(fd[0]);
					epoll_ctl(epfd,EPOLL_CTL_DEL,fd[0],&ev);
					break;
				}
				else
				{
					printf("%s\n",buf);
				}
			}
		}
	}
	return 0;
}


//epoll_server
//使用epoll监听多个fd
#include<stdio.h>
#include<sys/epoll.h>
#include<unistd.h>
#include"wrap.h"

int main()
{
	//创建套接字,绑定
	int lfd = tcp4bind(8000,NULL);
	//监听
	Listen(lfd,128);
	//创建树
	int epfd = epoll_create(1);
	//将lfd上树
	struct epoll_event ev,evs[1024];
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
	//while监听
	while(1)
	{
		int nready = epoll_wait(epfd,evs,1024,-1);
		if(nready < 0)
		{
			//出错
			perror("");
			break;
		}
		else if(nready == 0)
		{
			continue;
		}
		else
		{
			//有变化,与select不同的地方是epoll只需要看变化的
			for(int i = 0;i<nready;i++)
			{
				if(evs[i].data.fd = lfd && EPOLLIN)//lfd变化,只是读事件变化
				{
					struct sockaddr_in cliaddr;
					char ip[16] = "";
					socklen_t len = sizeof(cliaddr);
					int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
					printf("new client ip = %s port = %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16)
					,ntohs(cliaddr.sin_port));
					//上树
					ev.data.fd = cfd;
					ev.events = EPOLLIN;
					epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
					
				}
				else if(evs[i].events && EPOLLIN)
				{
					char buf[1024] = "";
					int n = read(evs[i].data.fd,buf,sizeof(buf));
					if(n < 0)
					{
						//出错
						perror("");
						//下树
						epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
					}		
					else if(n == 0)
					{
						printf("client close%s\n");
						//关闭
						close(evs[i].data.fd);
						//下树
						epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);
					}
					else
					{
						printf("%s\n",buf);
						printf(evs[i].data.fd,buf,n);
					}
				}
			}
			
		}
	}
	
	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅谈多路复用select、epoll 的相关文章

随机推荐

  • BES2500Y之开机进TWS配对

    使用场景 刚刚烧录程序的耳机 怎么实现开机自动进TWS配对 case APP POWERON CASE NORMAL if defined BTIF EARPHONE defined EARPHONE STAY BOTH SCAN if d
  • 【cocos creator 3.x】精灵图片不显示

    精灵图片不显示 现象 原因 解决方案 现象 在cocos creator 3 2版本的使用中遇到了精灵图片无法展示的几个场景 在prefab某个node下Sprite的图片无法显示 动态加载prefab时 某些节点的图片无法显示 原因 图片
  • JAVA关键字详解

    JAVA关键字详解 1 final数据 1 gt 对于基本类型前加以final修饰 表示被修饰的变量为常数 不可以修改 一个既是static有是final的字段表示只占据一段不能改变的存储空间 2 gt final用于对象应用时 final
  • tp5 数组进行分页

    首页引入文件 use think paginator driver Bootstrap
  • ES Aggs count distinct group by聚合排序查询

    ES Aggs count distinct group by聚合排序查询 1 kibana query hits限制了10000条 添加 track total hits true query 2 查询返回特定字段 source incl
  • string类的模拟实现

    namespace swx string需要考虑完善的增删查改 class string public typedef char iterator typedef const char const iterator const iterat
  • git 生成ssh key操作

    1 在终端输入 ssh keygen t rsa C taoxx 注 username为你git上的用户名 如果执行成功 返回 Generating public private rsa key pair Enter file in whi
  • 【java】【SSM框架系列】【五】【完】MyBatisPlus

    目录 一 MyBatisPlus简介 1 1 入门案例 1 2 MyBatisPlus概述 二 标准数据层开发 2 1 标准数据层CRUD功能 2 1 1 标准数据层CRUD功能 2 1 2 lombok 2 2 分页功能 2 3 开启My
  • 老曹眼中的CRM 图解

    CRM 是企业 以客户为中心 价值观的核心体现之一 各种2B的应用都无可避免 而企业应用具有一定的复杂性 理解CRM系统也不是轻而易举的事情 在老码农的眼中 CRM 系统可能是这样的 业务组成 业务单元的组成是第一位 业务单元是企业的实在需
  • 1496. Path Crossing

    typedef pair
  • 【OpenGL】OpenGL入门之纹理(Texture)

    目录 纹理 纹理环绕方式 纹理过滤 多级渐远纹理 Mipmap 加载与创建纹理 stb image h 生成纹理 应用纹理 纹理单元 参考 纹理 在此之前 我们已经可以为每个顶点添加颜色来增加图形的细节 从而创建出有趣的图像 但是 如果想让
  • 基于uiautomator的android脚本录制记录

    最近有个项目大概需求是 根据服务端发送的指令 去执行一些特定的操作采集数据后并上传给服务器 前后搞了10来天 总算是搞出来了 现在记录下 省的以后忘记 给以后有类似需求的小伙伴多点资料 有啥问题可以联系我QQ 442947949 带上备注
  • 函数的返回值-接收返回元组函数的方式

    def measure 测量温度和湿度 print 测量开始 temp 39 wetness 50 print 测量结束 元组 可以包含多个数据 因此可以使用元组让函数一次返回多个值 如果函数返回的类型是元组 小括号可以省略 return
  • 在QMap中嵌套QList

    刚接触QT的QMap比较困惑 看这名字以为是二维数组 因为我把QList当作一维数组来用了 事实上也确实可以 但只当一维数组太浪费了 可参考别的资料 cpp view plain copy QMap
  • 面试时,发现公司有这8个现象,建议你慎重考虑

    爱开发 陪伴你一起成长 快年底 相信有不少朋友有跳槽的念头 今天我们来聊一聊面试的一些话题 面试时 我们有机会对公司的情况做一下了解 比如和面试官交流 我们大致能了解到公司的一些基本情况 这些情况比我们在去面试前更为准确 程序员面试时 发现
  • Spark RDD之Key-Value类型操作详解

    partitionBy案例 1 作用 对pairRDD进行分区操作 如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区 否则会生成ShuffleRDD 即会产生shuffle过程 2 需求 创建一个4个分区的R
  • 了解链接是什么?

    链接是将各种代码和数据片段收集并且组合成为一个单一文件的过程 这个文件可以被加载到内存并且执行 链接可以执行于编译时 也就是在程序被加载器加载到内存并且执行 甚至在执行于运行的时候 也就是由应用程序来执行 在早期的计算机系统中 链接是手动执
  • OrCAD中DRC的使用简要说明

    OrCAD中DRC的使用简要说明 1 DRC的使用 Scope entire 检查所有设计 selection 检查选中部分 Mode 理解Mode需要先理解instance 实例 和occurrences 事件 这是OrCAD中非常重要的
  • 单元测试框架(JUnit和Unittest)

    单元测试就是针对最小功能单元编写测试代码 1 JUnit Unit 是一个 Java编程语言的单元测试框架 java程序最小的功能是方法 单元测试就是针对java方法的测试 测试单元中的每个方法必须可以独立测试 方法间不能有任何依赖 1 1
  • 浅谈多路复用select、epoll

    一 多路复用技术 在理解多路复用之前了解一下IO阻塞 IO非阻塞有利于理解IO多路复用 可以想象成父进程为董事长 其雇佣秘书 内核 帮助你监听读写缓冲区 常见的有select poll epoll 这里只谈一下select和epoll po