Java当中的线程I/O模型如图所示:
当一个线程进行I/O操作的时候,传统的做法是阻塞等待,直到I/O操作完成再继续后续的操作,这种IO方式就是BIO(Blocking I/O)。
BIO方式的缺点是:
JDK1.4引入了NIO(No Blocking I/O或者是New I/O)。NIO是一种同步非阻塞的I/O模型,相对于BIO,NIO允许一个线程在I/O操作的时候处理其他任务,但是需要定期轮询检查I/O操作是否完成。
NIO的缺点在于:
为了解决NIO的缺点,Linux引入了I/O多路复用的机制,即一个线程可以同时监听多个I/O操作,当某个I/O操作完成后,会通知线程进行处理。
多路指的是多个SOCKET连接之间的I/O操作,复用指的是共用一个线程。
I/O多路复用的优点在于:
需要注意的是,I/O多路复用只有和NIO配合使用才能发挥作用,因为NIO是非阻塞的,所以可以在一个线程中同时监听多个I/O操作,而BIO是阻塞的,一个线程只能处理一个I/O操作,所以无法实现I/O多路复用。
I/O多路复用的实现思路:
对于多个socket连接,程序提供一个文件描述符集合给系统,当某个接口的I/O操作完成后,会通知线程进行处理。
实现I/O多路复用的方式有三种:select
、poll
、epoll
。
函数原型如下所示:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
从参数可以看出来select
方式监听读、写、异常事件。
select
根据监听的事件类型分别创建三个文件描述符数组,然后在timeout时间内阻塞线程进行监听,直到有事件发生或者超时。之后检查数组中是否有事件到达。
select
的缺点在于:
select
需要将数组从用户空间拷贝到内核空间,同时重新对数组进行遍历查找,效率低;函数原型如下所示:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
本质的工作过程和select
类似,但是稍微做了改进,只需要构建一个数组,并且数组大小不受限制,而是能够自由指定;
poll
的缺点在于:
poll
之后都需要进行数组遍历,这一点并没有改进为了解决select
和poll
的缺点,在高并发场景下,不同的操作系统引入了不同的解决方案,例如Linux引入了epoll
、FreeBSD引入了kqueue
、Solaris引入了/dev/poll
。
由epoll
实现I/O多路复用,步骤如下:
int epfd = epoll_create(10);
其中,int epoll_create(int size)
会在内核空间开辟一块指定大小的数据表,并由epfd
指向这部分内存。
2. 创建好epoll对象之后,使用epoll_ctl
将注册需要监听的事件:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd
是创建数组之后的内存指针;op
是操作类型,包括三种模式:
EPOLL_CTL_ADD
:添加需要监听的事件;EPOLL_CTL_MOD
:修改需要监听的事件;EPOLL_CTL_DEL
:删除需要监听的事件;fd
是需要监听的文件描述符,需要支持NIO;event
记录了注册事件的具体信息。数据结构如下所示:typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
epoll_wait
进行监听:epoll_wait
函数原型如下所示:int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
epfd
是创建数组之后的内存指针;evlist
是用于存放事件的数组,也是返回的结果数组,包含被触发事件的对应文件描述符;
select
、poll
的区别,select
、poll
会返回所有文件描述符然后遍历,而epoll
只会返回被触发事件的文件描述符;maxevents
是监听事件的最大容量;timeout
是超时时间;block
的,也就是阻塞的,只有超时才会返回;epoll的优点在于:
AIO(Asynchronous I/O),即异步非阻塞I/O模型,AIO的实现方式是基于事件和回调机制的,当一个I/O操作完成后,会通知线程进行处理,因此不需要轮询操作。
AIO和NIO的区别在于:
select
、poll
、epoll
对比Q:NIO的具体含义
A:NIO一般理解为Not Blocking IO,即非阻塞IO,和传统的BIO(阻塞IO)相比,NIO模型中,一个线程在IO操作的时候可以处理其他任务,定期轮询检查IO操作是否完成
Q:基于什么实现的I/O多路复用?
A:传统的实现方式包括select
、poll
,但是这两类方法都需要遍历数组,效率较低,为此不同的操作系统提出了不同的改进方案,例如solaris提出了/dev/poll
,FreeBSD提出了kqueue
,Linux提出了epoll
,而epoll
相比于select
、poll
的主要区别就是返回的事件列表只包括触发事件的文件描述符,而不是全部监听事件的文件描述符,改进了数组遍历这一监听方式。