select/poll/epoll 学习

select

select通过设置或检查存放fd标志位的数据结构进行下一步处理.
对socket是线性扫描,即轮询,效率较低: 仅知道有I/O事件发生,却不知哪几个流,只会无差异轮询所有流,找出能读/写数据的流进行操作。同时处理的流越多,无差别轮询时间越长 - O(n)。

fd_set的实现就是采用位图bitmap
使用位图bitmap,往集合了添加n时只需将第n个bit位置1,移除n时只需将第n个比特置为0,移除所有数据时,只需将所有bit置为0。

fd_set *readset
fd_set *writeset
fd_set *exceptset

readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字
在这里插入图片描述
这也是为什么fdset不可重用的原因
bitmap在用户态只能用四个宏来操作,没办法正确将对应输出置0

/*申请了连续的内存后,平分到6个映射*/
fds.in      = bits;
fds.out     = bits +   size;
fds.ex      = bits + 2*size;
fds.res_in  = bits + 3*size;
fds.res_out = bits + 4*size;
fds.res_ex  = bits + 5*size;
/* 我们需要使用6倍于最大描述符的描述符个数,
* 分别是in/out/exception(参见fd_set_bits结构体),
 * 并且每份有一个输入和一个输出(用于结果返回) */

具体流程如下

select(fdr+1,&rdset,NULL,NULL,NULL);

fdr+1 是为了减少查找范围,要检测描述符8, 9, 10, 把select的第一个参数定为11, 实际上会检测0-10,如果你把select的第一个参数定为8, 实际上只检测0到7, 所以select不会感知到8, 9, 10描述符的变化。
(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set将第5位置为1)
(3)若再加入fd=2,fd=1,则set再将1、2位置为1
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回。
在这里插入图片描述
select的优点:
可以等待多个套接字
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大(fdset不可重用,用户态<->内核态开销大)
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大(时间复杂度O(n))
(3)select支持的文件描述符数量太小了,默认是1024

poll

struct pollfd {
int fd;        /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
};

fd:特定的文件描述符,若设置为负值则忽略events字段并且revents字段返回0。
events:需要监视该文件描述符上的哪些事件。
revents:poll函数返回时告知用户该文件描述符上的哪些事件已经就绪。

扩展
poll没有限制是因为在内核基于链表实现的,注意是内核
在用户态他还是数组
在内核态 struct poll_list类型的链表walk啊,这是个在内核空间开辟出来的链表,而我们传入的从用户空间复制来的pollfd则通过copy_from_user方法拷贝给了walk链表

int poll (struct pollfd *fds, size_t nfds , int timeout);

参数说明:

fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于 socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select() 函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;

nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

timeout:是poll函数调用阻塞的时间,单位:毫秒;

事件的添加

poll的升级在它可以将事件进行具体的划分,即增加多个事件种类。注意,这里的events是short类型,但是我们也可以将它看成一个位图结构,方便我们进行事件的添加。添加事件的本质就在于按位与或上一个表示某一个事件的宏。

在poll函数返回后,可以通过 与 运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪。

解决了什么问题

fdset是不可重用问题
select 支持的文件描述符数量1024

Epoll

epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait

调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个 红黑树(rbr) 用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表(rdlist),用于存储准备就绪的事件.
epoll_ctl 对红黑树操作,添加所有socket节点。
Epoll将所有你关注的事件存放在统一的事件集合里面。
当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已.

level-triggered 和 edge-triggered

读缓冲区刚开始是空的
读缓冲区写入2KB数据
水平触发和边缘触发模式此时都会发出可读信号
收到信号通知后,读取了1kb的数据,读缓冲区还剩余1KB数据
水平触发会再次进行通知,而边缘触发不会再进行通知
所以,边缘触发需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到EGAIN为止,EGAIN说明缓冲区已经空了,因为这一点,边缘触发需要设置文件句柄为非阻塞.

Epoll

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值