- 一、IO是什么
- 二、IO流的分类
- 三、字节流和字符流的区别:
- 四、BIO,NIO,AIO 有什么区别
- 五、什么是IO多路复用
- 六、NIO监听socket数量有没有限制
- 七、select/poll/epoll之间的区别
- 八、epoll 水平触发(LT)与 边缘触发(ET)的区别
一、IO是什么
Linux 系统中一切皆文件,而文件指的就是一些二进制的流,我们在做进程间通信时,都是对这些流进行操作,简称 IO 操作。在操作系统的层面,这些操作都是通过文件描述符来进行操作的。
通常用户进程的一个完整 IO 分为两个阶段:从用户空间到内核空间,从内核空间到设备空间。
- 内核空间:存放的是内核代码和数据
- 用户空间:存放的是用户程序代码和数据
以输入操作为例,用户进程进行了IO操作后,内核会先看缓冲区有没有数据,如果没有,则从设备空间读取,因为设备IO是比较慢的,所以会存在一个等待的过程。
所以 IO 操作的流程大致分为两步,等待 IO 操作就绪、数据拷贝
IO 一般有三种,内存 IO 、网络 IO、磁盘 IO。
Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/OutputStream: 字节数入流、字节输出流。
- Reader/Writer: 字符输入流、字符输出流。
- 字节流默认不使用缓冲区;字符流使用缓冲区
- 编码不同:
1、字节流的处理:采用ASCII编码,主要用在处理二进制数据,它是按字节来处理的但实际中很多的数据是文本。
2、字符流的处理:采用Unicode编码,按虚拟机的encode来处理,也就是要进行字符集的转化。
要理解三者区别,主要从同步/异步、阻塞/非阻塞 两个维度来理解:
-
同步:java自己去处理io。
-
异步:java将io交给操作系统去处理,告诉缓存区大小,处理完成回调
-
阻塞:使用阻塞IO时,Java调用会一直阻塞到读写完成才返回。
-
非阻塞:使用非阻塞IO时,如果不能立马读写,Java调用会马上返回,当IO事件分发器通知可读写时在进行读写,不断循环直到读写完成。
BIO:Block IO 同步阻塞IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞IO ,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了IO多路复用,但IO操作还是同步操作。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞IO ,异步 IO 的操作基于事件和回调机制。
五、什么是IO多路复用以socket.read()为例子:
- 传统的BIO,如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
- 对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
- 最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。
- 换句话说,BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了",在AIO模型里用户更需要关注的是“读完了”。
IO多路复用是一种同步非阻塞 IO 模型,实现一个线程可以监视多个文件句柄,一旦某个文件句柄就绪,就能通知应用程序进行相应的读写操作,没有文件句柄就绪时会阻塞应用程序,交出 CPU。多路指的是网络连接,复用指的是同一个线程。
在Java代码层面,NIO包提供了一个选择器selector,我们把需要检查的Socket连接注册到这个selector中,然后主线程会阻塞在Selector#select方法里,当选择器发现某个socket就绪了,就会唤醒主线程。然后我们可以通过selector获取到就绪状态的socket进行相应的处理。
六、NIO监听socket数量有没有限制- select 使用的是bitmap表示需要检查的socket集合,因此有1024的限制。
- poll 使用数组就没有这个限制了,它就可以让咱们线程监听超过1024个socket限制
select缺点:
- 单个进程所打开的FD是有限制的,通过FD_SETSIZE设置,默认1024(32位机默认是1024个。64位机默认是2048)
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 对socket扫描时是线性扫描,采用轮询的方法,效率较低(高并发时)
poll 缺点:与select相比,使用了数组,因此没有fd的限制,其它基本一样。
epoll 在poll方法上,针对文件句柄操作进行了改进。
- 做到了输入输出参数分离,只需要一次将文件描述符拷贝到内存中,减少拷贝过程,不像 select 每次循环都要传入需监听的fd列表。
- 在内核空间创建了epoll_event的结构,划分了两块区域,存放需要监听的fd列表和就绪列表,存放就绪状态的socket信息。
- epoll 使用事件回调机制,epoll_wait 会直接访问就绪队列就知道哪些文件描述符就绪了,不需要像 select、poll 使用轮询方式再次进行遍历。
- 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
Redis和Nginx使用的IO多路复用技术,就是epoll。
多路I/O复用不管是select,poll还是epoll,其都是通过同时监听多个文件描述符,当有文件文件描述符处于就绪状态时,触发通知
LT(Level Trigger,水平触发)模式和ET(Edge Trigger,边沿触发)模式是两种文件描述符准备就绪的通知模式
LT模式 水平触发通知:只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作。
ET模式 边沿触发通知:如果文件描述符自上次状态检查以来有了新的I/O活动(比如新的输入),此时才会触发通知。
-
ET 的性能比 LT 性能更高,因为 epoll_wait 返回的次数少了很多。但同时,ET需要一次完成所有数据的读取,因为下次读取时,不会再返回了。
-
select和poll只支持LT工作模式,epoll的默认的工作模式是LT模式。
Nginx 默认采用 ET 触发来使用epoll。Redis使用的是LT触发
Netty 也使用的IO多路复用,待研究



