引入:
这个函数功能和下面讲的select很像
select
首先注意到select函数,可以发现有五个参数,从左往右分别是接受文件的最大文件描述符,读文件集合,写文件集合,异常文件描述符,超时时间,注意rset,再上方有一个FD_SET操作,这一步是干什么呢?首先rset具体类型是一个bitMap,什么是bitMap,个人理解为位图,如下图
这一步主要是把文件中是否拥有数据,是否需要被监听的这些信息储存在bitMap中(1代表有数据,0代表无数据),所以得到了这个重要的bitMap后,会将用户态的bitMap传到内核态的bitMap,在select函数中交给内核态来判断,是否需要读文件呢?
值得注意的是,select也一个阻塞函数,如果传过来的bitMap全是0的话,select函数会一直阻塞,直到有数据读取时。
当有数据的时候,select函数会将fd置位,即bitMap置1,然后读取完成后返回select(已置位),即执行到下一行。
然后会跑到下面这个for循环,通过FD_ISSET判断哪一个fd被置位,然后将它处理(put)
解释完了rset,再来看看max,加入我们前面的描述符集合,分别是1,3,5,7,9,那么我们将max + 1 就是 10,即bitMap的大小会变成10位,方便我们置位。
select的缺点:
①bitMap默认大小只有1024
②在FD_SET之前每次都得清零(FD_ZERO),那么意味着我们不能重用bitmqp
③在整体拷贝bitMap到内核态时是仍会有一定的开销的(比我们for循环一个一个判断要好得多)
④最后仍需要进行一次O(n)的遍历
poll
poll函数的改动都是围绕结构体展开的!poll函数也是阻塞的!
那我们必然来看看结构体的组成
struct pollfd{
int fd;
short events;
short revents;
}
第一个就是文件描述符,第二个是当前的事件(读?写?读写?),第三个是对events的回馈(相当于我们上面的bitMap),当执行poll的时候,会对revent置位,就和select上面的功能是一样的,判断是否有数据。
再看执行完了poll函数之后,会对原先有数据的置revent零,注意此时的fd和event都是原先的,是可以重用的!这就解决了刚才上面select的第二个缺点。
注意在select的bitMap是上限1024的,但是我们这一次是结构体数组,容量也增大了!
poll缺点:
③在整体拷贝类似于bitMap到内核态时是仍会有一定的开销的(比我们for循环一个一个判断要好得多)(还是得靠revent来判断!)
④最后仍需要进行一次O(n)的遍历
epoll
可以看到顶部有一个epfd = epoll_create(0),我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件。
然后在虚线上的for循环中,对epfd写入数据,看epoll_ctl函数的第二个参数,代表增加写入!该方法写入了文件描述符和事件(读?写?),但是可以看到和上面poll方法的差别,没有revent字段(即类似于bitMap的那个玩意)!
所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把这样的事件放到上面的rdllist双向链表中。
当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。