select\poll\epoll实现原理

I/O多路转接原理

—先构造一张有关文件描述符列表,将要监听的文件描述符添加到该表中(网络通信即网络I/O需要套接字对应的文件描述符)
客户端连接到服务器会产生对应的用于通信的cfd,该cfd对应两个内核缓冲区(一个读缓冲区一个写缓冲区),当客户端有数据发送过来则检测读缓冲区即可
—调用一个函数监听该表中的文件描述符,直到这些描述符表中的一个进行I/O操作时,该函数才返回
—返回时告诉进程有多少(哪些)文件描述符要进行I/O操作
(类似于信号屏蔽过程中,内核有未决信号集和阻塞信号集,设置阻塞就是设置阻塞信号集,用户自定义一个信号集表里面的标志位是sigset_t类型,与阻塞信号集格式一致通过sigsetadd()设置标志位,且与阻塞信号集中的信号也一一对应,通过sigprocmask()把该表设置到阻塞信号集)
—简言之:再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件
—实现方法分为:select/poll/epoll

SELECT

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds: 		监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:	监控有读数据到达文件描述符集合,传入传出参数
writefds:	监控写数据到达文件描述符集合,传入传出参数
exceptfds:	监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout:	定时阻塞监控时间,3种情况
			1.NULL,永远等下去
			2.设置timeval,等待固定时间
			3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
	long tv_sec; /* seconds */·
	long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); 	//把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); 	//测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); 	//把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); 			//把文件描述符集合里所有位清0

nfds:nfds:多个客户端连接时都会对应一个文件描述符,nfds为最大文件描述符加1(也可以传1024,内核检测时是遍历线性表,1024则遍历到最后,1024的原因是fd_set的内核实现是通过一个数组,大小为1024,想修改需要重新编译内核重新设置FD_SETSIZE)
—fd_set* readfds 传入传出参数。一般只用这个,文件描述符的操作只有读写异常,写操作时主动的,所以一般也不考虑接收数据是被动行为,所以需要一直检测是否有数据,放入读集合,内核只会检测该fd的读缓冲区
fd_set的0号标志位对应了文件描述符的0,一一对应过去。若100号文件描述符对应的fd_set标志位为1,则去检测100号文件描述符的缓冲区(读集合则检测读缓冲区,若有数据,则认为该文件描述符对应的客户端发数据)

—fd_set* writerfds 和fd_set* exceptfds同理

timeval结构体,注意赋值时两个都要赋值,否则不赋值的那个为随机数,会出现错误,timeout参数设置select是否阻塞,为NULL则永久阻塞,检测到文件描述符变化时返回。timecal a(a,tv_sec =0;a,tv_usec=0则不阻塞,通过赋不同值设置返回时间,一般都写NULL)
在这里插入图片描述
—select实现原理:假设客户端ABCDEF连接到服务器,分别对应文件描述符为3.4.100.101.102.103.(均为server端用于通信的fd)通过select检测客户端是否发数据:

  1. 建立fd_set reads 表 处于用户空间
  2. 通过FD_SET(3,&reads);全部放入
  3. 调用select函数
    select(103+1,&reads,null,null,null);
    假设ABC发送了数据内核会拷贝一份fd_set(注意012时标准输入输出,套接字操作一般从3开始),内核会检测0-103号描述符,检测到为1的标志位,则会去找对应的文件描述符,查看其对应的读缓冲区是否有数据,如ABC有数据,DEF无数据则内核会把fd_set表DEF对应的标志位变为0得到内核检测完成后修改完毕的表再拷贝到覆盖用户空间原来的fd_set表reads,再去遍历reads可以判断哪个fd有变化,然后可以对应接收数据I/O操作。因为reads会被覆盖,需要定义两张fd_set 表 temp用于存储原来的标志位用以后续遍

—select多路转接的伪代码实现

int lfd = socket();
bind();
listen()
//创建文件描述符表
fd_st reads,temp;
//初始化
fd_zero(&reads)
//监听的文件描述符在有连接请求的时候读缓冲区是会有数据的,也需要检测,将监听的lfd加到读集合
fd_set(lfd,&reads);
//委托内核做I/O检测
int maxfd = lfd;
while(1)
{
temp = reads;
int ret = select(maxfd+1,&temp,NULL,NULL,NULL);

//`主要有连接和通信两种文件描述符,判断是不是监听的
if(fd_isset(lfd,&temp))//lfd唯一,若在传出的temp中则存在新连接
{
int cfd = accept();//不会阻塞
//cfd加入读集合
fd_set(cfd,&reads);
//更新maxfd
maxfd = maxfd<cfd?cfd:maxfd;//如果当即cfd已经写数据检测不到,下一个循环可以检测到
}
//客户端发送数据,检测通信的cfd
for(int I = lfd+1;i<=maxfd;++i)/一般lfd会为3,遍历小于maxfd的文件描述符
{
if(fd_isset(I,&temp))
{
int len = read();
if(len ==0);//对方点开连接,清除该cfd
{
fd|_clr(I,&reads);//从原始表删除
}
write();
}
}

EPOll

—int epoll_creat(int size) 生成一个树根节点,epoll专用的文件描述符epfd, size是最大描述符数。epoll内部维护的是一个红黑树的数据结构
——int epoll_ctl(int epfd,int op int fd struct epoll_event * event);
控制某个epoll文件描述符时间可以注册删除,修改
epfd:epoll_creat生成的epoll专用描述符
op: epoll_CTL_ADD — 注册epoll_CTL_ MOD—修改 epoll_CTL_DEL—修改
fd:指定一个文件描述符
epoll_event是一个结构体,
epoll_event
{
unit32_t events;//events是 读写异常三个事件 用按位或方式多选属性
epoll_data_t data;其中epoll_datat是一个联合体,一般只对其中的fd赋值且取与第三个参数一样,传入参数
}

typedef union epoll_data {
void * ptr
int fd;
unit32_t u32;
unint64_t u64;
}epoll_data_t data;
联合体,共用一块内存,使用时只能赋值一个,一般使用fd,fd的赋值取epoll第三个参数一样

epoll_event这个结构体主要是对第三个参数的描述,传给epoll_ctrl,epoll_ctl会把该结构体拷贝一份挂载于epoll树上,ptr指针可以指向一个结构体,里面可以存放更多相关信息(例如想知道发生变化的fd的端口和IP则可以自定义一个结构体并用指针指向,里面可以存放fd,相应的ip和端口)

epoll内部维护的是一颗红黑树,挂载的就是epoll_event这个结构体,该结构体被复制一份挂载挂载到树上往epoll上挂载的是一个结构体epoll_event

在这里插入图片描述
—epoll_wait(int epfd,struct epoll_event* events,int maxevents, timeout)委托内核进行检测的函数
struct epoll_event* events:数组,传出参数,无论是动态数组还是链表,里面存的都是void*类型的指针,
maxevents :数组的最大值
timeout :与poll类似

—epoll的伪代码实现
伪代码的实现
int main()
{
//创建监听套接字
int lfd = socket();
//绑定
bind();
//监听;
//epoll树根节点
int epd=epoll_creat(3000);
//存储发生变化的fd对应信息
strict epoll_event all[3000];
//初始化
//监听的lfd挂载到epoll树上
struct epoll_event ev;
//在ev中初始化lfd信息
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_crl(epfd,EPOLL_CTL_ADD,lfd,&ev)
while(1)
{
//委托内核检测事件
int ret = epoll _wait(epfd,all,3000,-1);
//根据ret遍历all数组
for(inti=0;i<ret;++i)
{
int fd = all[I].data;fd
//有新的连接
if(fd = lfd)
{
//接收连接请求-accept
int cfd = accept();
//cfd上树
ev.events = EPOLLIN;
ev.data.fd=cfd;
epoll_ctl(epfd,epoll_ctl_add,cfd,&ev);
}
//已经连接的客户端有数据发送过来
else
{
//只处理客户端发来的数据
if(all[i].events&EPOLLIN)
{
continue
}
//读数据
int len = revc();
if(len==0)
{
close(fd);
//检测的fd从树上删除
epoll_ctl(epfd,epoll_ctl_del,fd,NULL);
}
//写数据
send();
}
}

}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
selectpollepoll都是用于多路复用IO的机制,用于同时监听多个文件描述符的就绪状态。它们的原理和区别如下: 1. selectpoll的原理是通过遍历所有需要监听的文件描述符来检查是否就绪。select使用fd标注位来存放需要监听的文件描述符,而poll使用链表来存储文件描述符。因此,select会受到最大连接数的限制,而poll不会。而epoll采用回调机制,只需要处理已经就绪的文件描述符。 2. selectpoll在返回时不会明确指出哪些文件描述符已经就绪,需要在程序中遍历所有监听的文件描述符来找到就绪的文件描述符。而epoll会直接返回已经就绪的文件描述符,省去了遍历的步骤。 3. selectpoll采用轮询的方式来检查文件描述符是否就绪,效率会随着文件描述符数量的增加而线性降低。而epoll采用回调机制,在活跃的socket很多时效率不会受到太大影响。 4. epoll支持边缘触发和水平触发两种模式,边缘触发模式效率更高。而selectpoll只支持水平触发模式。 5. selectpoll需要将相关的文件描述符的数据结构拷贝进内核,再拷贝出来。而epoll的文件描述符数据结构存储在内核态中,利用mmap文件映射内存加速与内核空间的消息传递,减少了复制的开销。 总结来说,selectpoll使用轮询方式来检查文件描述符是否就绪,效率较低,而epoll采用回调机制,效率更高。epoll支持边缘触发模式和水平触发模式,而selectpoll只支持水平触发模式。epoll的文件描述符数据结构存储在内核态中,减少了复制开销。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [epollpollselect的原理和区别](https://blog.csdn.net/wwwvipp/article/details/119888373)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [[14本经典Android开发教程]-8-Linux内核阅读心得体会](https://download.csdn.net/download/cleopard/8391591)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值