BIO
BIO即为阻塞IO调用,想要read一个文件或者write一个文件,都是通过文件描述符即FD去关联。
现在有一个线程想要读取FD8,当FD8没有数据返回时,这个线程就阻塞着,没有数据就等,有数据就返回。
一个线程对应一个连接也就是管理一个FD,当现有线程阻塞时。想要读取新的文件只能通过开辟新的线线程,这就是BIO时期
NIO
NIO非阻塞同步io,由于线程数量是有限的。
NIO时期,线程不会阻塞,一个线程可以通过轮询的方式去查询,获取哪个fd有数据返回。
系统分为用户空间和内核空间,轮询发生在用户空间,线程虽然不会阻塞,但是一次查询就是一次系统调用,当线程需要轮询的fd数量变多,带来的成本问题随之变高。
NIO的问题在于,线程并不知道哪个FD有数据了,只能拿着一个一个去问,那么能不能变成有数据的主动通知,答案是可以的,这就引入了SELECT
SELCET
假如现在有1000个fd,线程统一把这些fd传给select,由内核监控哪些有数据返回,然后线程再拿着准备好的fd再来调用,避免了用户空间发生的轮询带来的成本,但是随之产生的问题是fd相关数据来回在用户空间和系统空间拷贝。
EPOLL
为了解决SELECT带来的问题,引入EPOLL,即在用户空间和系统空间开辟出一个共享空间,
epoll是一个整体,包含epoll_create 、epoll_ctl、epoll_wait三个系统调用。
共享空间,进程把fd存放红黑树,内核通过红黑树拿fd去查哪个io数据到达,把到达的放到链表里。然后进程从链表取对应的fd。
大致过程如下:
1.进程先调用epoll的create,创建一个epoll文件描述符;
epoll通过mmap开辟一块共享空间,增删改由内核完成,查询则内核和用户进程都可以
这块共享空间中有一个红黑树和一个链表
2.进程调用epoll的ctl add/delete sfd,把新来的链接放入红黑树中,
2.1进程调用wait(),等待事件(事件驱动)
3.当红黑树中的fd有数据到了,就把它放入一个链表中并维护该数据可写还是可读,wait返回;
4.上层用户空间(通过epoll)从链表中取出fd,然后调用read/write读写数据.