网络连接在内核中是以文件描述符fd来表示的
while(1){
for(Fdx in (FdA ~ FdE)){
if(Fdx 有数据){
读取Fdx的数据并进行处理
}
}
}
简单粗暴解决问题
缺点:判断是否有数据是由程序判断的,效率较低
select
准备文件描述符的数组fds
dfs中的每个元素其实是一个数,这个数代表着文件描述符的编号,这个编号并不是按顺序的可能是随机的数
将最大的文件描述符存入到max中
select(最大文件描述符+1,读文件描述符集合,写文件描述符集合,异常文件描述符集合,超时时间)
读文件描述符集合是一个bitmap,用来表示哪一个文件描述符是被启用的(被监听的),默认1024个位
例如存入的文件描述符为:1 2 5 7 9
那么bitmap的存储为:0 1 1 0 0 1 0 1 0 1
即bitmap中涵盖的fdx的所有信息
select执行流程
程序是运行在用户态空间的,但是select将用户态的rset拷贝到内核态,由内核态判断每一个fdx是否有数据来
如果没有数据,那么内核态就一直在这判断,程序呈一个阻塞状态,即select是一个阻塞函数如果没有任何数据到来那么程序将会一直阻塞在select
当有数据到来时
- 内核将会再有数据的FD置位
- select返回
- 遍历FD,判断哪一个FD被置位,将置位的FD读出进行相应的处理
总结:我们将文件描述符收集过来,交给内核,让内核帮我们判断哪一个有数据,当里面任何一个或多个有数据的时候,select函数会返回,并且有数据的fd会被置位,返回之后fd集合,判断一下到底哪一个被set了,即被set的是有数据的,这个时候我们读取fd中的数据,并进行相应的处理,提高效率最主要的一点就是,他将fd放到了内核态,让内核帮我们判断哪一个fd有数据。
缺点:
- fd的大小为1024,虽然可以调整大小,但依然是有上限的
- 在判断置位后,fd的数据被修改,所以在此循环是需要将bitmap重新置位
- 虽然比每次用户态切换到内核态好一些,但将rset拷贝到内核态依然有很大的开销
- select函数返回时,我们知道有位被置位,但是不知道哪一个位被置位,需要再次遍历一遍,O(n)复杂度
poll
poll(pollfd结构体的数组,pollfd数组有几个元素,超时时间)
poll的工作原理和select相同
pollfd结构体:
- fd:文件描述符
- event:在意的事件(读时间、写事件)
- revents:对event的回馈
当有数据时
- 将fd置位:将pollfd中的revents字段置位
- poll返回
- 判断revents是否有数据,进行数据的读取和处理(revents置位,可重用)
poll解决了select的缺点:1、2、
epoll
epoll_create:创建epfd(epollfd)(理解成创建白板)
epoll_ctl:对epoll进行配置,相当于在白板上写字
EPOKK_CTL_ADD:加一行字
ev.data.fd:文件描述符
ev:对应的事件
epoll中的结构体是epoll_event格式,其同样拥有fd和event,但是没有revents字段
epoll_wait:
用户态和内核态共享epfd
解决了缺点(3)
epoll_wait也是阻塞状态的(水平触发),当有数据时
- 置位,通过重排,把有数据的重排至前面
2. 返回,值为一共有多少个FD触发了事件,按照此例题,则返回3,所以复杂度为O(1)