多路转接模型:select,poll,epoll

多路转接IO

概念

描述转接IO,对大量描述符的IO状态进行监控,使得程序能够知道哪个描述符IO是就绪的,可以进行IO操作,直接让程序针对就绪的描述符进行IO操作,提高效率(多用于网络通信服务器)

IO就绪状态:

可读:套接字接收缓冲区中,数据大小高于低水位标记(默认1字节)

可写:套接字发送缓冲区中,剩余空间大小大于低水位标记(默认1字节)

select模型

实现思路

在内核中遍历所有监控的描述符,判断IO就绪状态

操作流程
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
最大描述符个数+1(数组中遍历到nfds-1就可以,用来提高遍历效率);可读事件集合;可写事件集合;异常事件集合;timeout是超时等待时间,超过这个时间没有描述符接口就会返回;返回值:出错返回-1,超时返回0,有描述符返回大于0

1.程序员定义描述监控集合(可读、可写、异常),fd_set结构体中有个数组当作位图来使用,默认比特位个数1024,由__FD_SETSIZE宏控制

2.初始化集合后,将对应监控状态的描述符添加到对应状态集合中

3.将集合中的数据拷贝到内核中进行遍历判断监控(因为描述符对应的缓冲区都是在内核中的,在select系统调用接口内才能访问)。等到监控超时或有描述符就绪,监控返回,返回前将集合中没有就绪的描述符移除

4.监控返回后,select返回0不是没有就绪的描述符(超时);返回大于0表示有就绪的描述符,此时判断哪个描述符还在集合中,对应就绪的哪个状态

应用场景

同多路转接模型,针对单个描述符的IO状态进行监控也可以,因为select有超时控制

接口
void FD_ZERO(fd_set *set);初始化清空描述符集合
void FD_SET(int fd, fd_set *set);将fd描述符添加到set
int FD_ISSET(int fd, fd_set *set);判断fd是否还在集合中
void FD_CLR(int fd, fd_set *set);从set移除fd
优缺点
优点:

遵循POSIX标准,跨平台移植性好

缺点:

1.监控描述符数量有最大上限(取决于位图比特位个数默认1024,由__FD_SETSIZE宏控制)

2.每次都需要向集合中重新添加描述符,并需要将数据拷贝到内核(因为每次返回都移除未就绪的描述符)

3.监控原理是在内核中轮询遍历判断,所以select针对大量描述符效率会降低,

​ 实际项目中select多用于单个描述符超时控制,控制大量描述符一般epoll+线程池组成事件器

4.返回值是一个就绪的描述符集合,用户不能直接操作就绪描述符,还需要再遍历一次集合才能获取就绪的描述符

poll模型

针对每个描述符构建一个事件结构,描述要监控哪个描述符,监控什么IO状态,然后在内核遍历判断

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:描述符的事件结构数组首地址,struct pollfd{int fd监控的描述符, short events要监控的事件(POLLIN可读,POLLOUT可写), short revents实际就绪事件};
dfds:fds中监控的有效事件结构的个数,决定要向内核中拷贝多少数据
timeout:超时等待事件,单位为毫秒
返回值:小于0表示监控出错,0表示监控超时,大于0表示就绪时间的个数
操作流程

1.程序员定义事件结构体数组

2.从第0个元素开始,添加要监控的描述符以及对应事件(POLLIN可读,POLLOUT可写)

3.调用poll开始监控,原理:将有效的事件结构体拷贝到内核中进行轮询遍历判断是否就绪,等到等待超时 或有描述符就绪了要监控的事件,调用返回,并将就绪事件放入revents

4.调用返回后,遍历事件结构体数组,通过每个元素的revents获知对应描述符就绪了什么事件,对应进行操作

优缺点
优点:

1.采用事件结构方式,简化了多个描述符操作集和

2.可监控描述符没有数量上限

缺点:

1.跨平台移植性差

2.监控原理依然是遍历,监控性能会随着描述符增多而下降

3.监控调用返回后,依然需要遍历数组,才能获取到就绪的描述符

poll相较于select实用性更窄,不仅没有提升性能,反而失去了跨平台

epoll模型

最好用

操作流程

1.程序中调用int epoll_create()在内核中创建epoll句柄(struct eventpoll)(epoll各项描述符的监控信息都放在内核中,不需要重复添加)

int epoll_create(int size);size决定了所能监控的描述符数量上限,在内核2.6后弃用使用了动态增长

2.为每一个需要监控的描述符组织事件结构体,添加到内核的eventpoll结构中的数据结构(红黑树,效率较高)

struct eventrpoll{
	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 *evs)
EPOLL_CTL_ADD/DEL/MOD:添加/删除/修改描述符信息

3.调用epoll_wait开始监控,等到有描述符就绪或超时则调用返回

int epoll_wait(struct epollevent *evs接收就绪的描述符事件结构信息,结构体数组的首地址, int maxevents每次想要获取最大事件的个数不大于数组节点个数, int timeout最大等待时间)

监控原理:采用异步阻塞操作

​ 调用epoll_wait开始监控,给每一个描述符的就绪事件设置一个回调函数,描述符就绪时添加到rdllist链表中

​ 监控是异步操作,由操作系统操作,当有数据到来或缓冲区有空间,调用回调函数添加到rdllist链表

​ epoll_wait在用户态只是每隔一段时间判断rdllist链表是否为空,通过是否为空判断是否有描述符就绪,不为空将节点拷贝到evs,再调用返回

4.监控返回后,根据epoll_wait返回值遍历evs数组,其中放置的都是就绪的描述符对应事件节点,直接操作描述符

图解:

在这里插入图片描述

IO状态触发模式
水平触发:

默认触发方式,select和poll只有水平触发

对于可读事件,接收缓冲区有数据,就触发就绪;对于可写事件,发送缓冲区有空间,就触发就绪

边缘触发:EPOLLET

对于可读事件,只有新数据到来时才会触发一次事件(如果一次数据到来没有一次性读完,不会有第二次);

对于可写事件,只有缓冲区空闲空间从无到有时,才触发一次事件

要求最好一次将缓冲区中数据完全读取。由于不知道缓冲区有多少数据,所以进行循环读取,如果没数据,recv阻塞(很严重的问题)

​ 解决方法:将套接字描述符的非阻塞属性int fcntl(int fd, int cmd, …);开启,没有数据时立即返回报错

使用场景:

特定情况才使用边缘触发,比如当缓冲区中数据不完整,使用水平触发会一直触发条件件但不能操作,边缘触发可以从缓冲区中读取到一条完整的数据,提升效率

优缺点
优点:

1.所能监控的描述符数量不限制

2.所有描述符事件只用向内核拷贝一次

3.监控原理是异步阻塞,由系统完成监控。不是轮询遍历,不会因为描述符增多使性能下降

4.直接返回就绪的描述符和事件信息,程序中直接遍历,没有大量空遍历操作

缺点:

跨平台移植性差,只有Linux有

多路转接模型适用场景

1.有大量描述符需要进行事件监控(适用epoll)

2.有单个描述符需要进行读写超时控制(使用select、poll)

3.在大量描述符监控情况下,只适用于同一时间只有少量描述符活跃的场景

​ 因为多路转接模型是在就绪的描述符中进行轮训操作,活跃的描述符比较多,后面的描述符等待时间会很长

​ 处理大量活跃描述符,需要搭配线程池处理(就绪的描述符放入线程池处理)

优点:

多线程均衡调度是由系统负责的(时间片轮转,负载均衡)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值