Java I/O的演进之路
Linux网络I/O模型
根据UNIX网络编程对I/O模型的分类,UNIX提供了5种I/O模型。
阻塞I/O模型
非阻塞I/O模型
I/O复用模型
信号驱动I/O模型
异步I/O模型
Java NIO中的核心类库多路复用器Selector是基于epoll的多路复用技术实现。
Linux中,epoll是事件驱动,而select/poll则是顺序扫描,且支持的fd(文件描述符)有限。
epoll是Linux下的一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄。
先看看使用c封装的3个epoll系统调用:
int epoll_create(int size)
epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll_ctl可以操作epoll_create创建的epoll,如将socket句柄加入到epoll中让其监控,或把epoll正在监控的某个socket句柄移出epoll。
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout)
epoll_wait在调用时,在给定的timeout时间内,所监控的句柄中有事件发生时,就返回用户态的进程。
epoll初始化时,会向内核注册一个文件系统,用于存储被监控的句柄文件,调用epoll_create时,会在这个文件系统中创建一个file节点。同时epoll会开辟自己的内核高速缓存区,以红黑树的结构保存句柄,以支持快速的查找、插入、删除。还会再建立一个list链表,用于存储准备就绪的事件。
当执行epoll_ctl时,除了把socket句柄放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后,就把socket插入到就绪链表里。
当epoll_wait调用时,仅仅观察就绪链表里有没有数据,如果有数据就返回,否则就sleep,超时时立刻返回。
epoll的两种工作模式:
LT:level-trigger,水平触发模式,只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket。
ET:edge-trigger,边缘触发模式,只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
Linix中的IO多路复用技术
IO多路复用技术是指:将多个IO的阻塞复用到同一个select的阻塞上,从而使系统在单线程的情况下,可以同时处理多个客户端请求。
epoll相比select作了很多重大改进:
支持一个进程打开的socket fd 不受限制,仅受限于操作系统的最大文件句柄数
IO效率不会随fd数目的增加而线性下降(epoll实现了伪AIO)
使用mmap加速内核与用户空间的消息传递
API更加简单
Java 的IO演进
JDK1.0~JDK1.3:BIO
所有Socket通信都采用了同步阻塞模式(BIO),这种一请求一应答的通信模型简化了应用开发,但牺牲了性能。
JDK1.4:NIO
提供了很多进行异步IO的API和类库
ByteBuffer:进行异步IO操作的缓冲区
Pipe:进行异步IO操作的管道
Channel:如ServerSocketChannel和SocketChannel
多种字符集的编码和解码
多路复用器selector
文件通道FileChannel
但对文件的处理能力不足:
没有统一的文件属性(例如读写权限)
API能力较弱,比如目录的级联创建和递归遍历,要自己实现
所有的文件操作都是同步阻塞调用,不支持异步文件的读写操作
底层存储系统的一些高级API无法使用
JDK1.7:NIO2.0
相比NIO,改进有:
能够提供批量获取文件属性的API
提供了AIO功能
提供了通道功能,包括对配置和多播数据报的支持等
Last updated