NIO 基础
1、基本概念
四个相关概念:
- 同步(Synchronous)
- 异步( Asynchronous)
- 阻塞( Blocking )
- 非阻塞( Nonblocking)
- 同步/异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 。
- 所谓同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。
- 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果
- 阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
- 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
- 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
总结:
阻塞/非阻塞: 进程在访问数据的时候,缓冲区中的数据是否准备就绪。
阻塞: 往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里面。
非阻塞:当我们的进程访问数据缓冲区的时候,数据没有准备好,直接返回不需要等待,数据准备好了,也直接返回。
同步/异步: 基于应用程序和操作系统发出的IO事件锁采用的方式
同步:应用程序要直接参与IO读写的操作,同步的方式在处理IO事件的时候必须阻塞在某个方法上面等待IO事件完成(阻塞事件或者通过轮询IO事件的方式)
异步:所有的IO读写都交给操作系统去处理,由于所有的IO读写都交给了操作系统,所以它可以去做其他的事情,并不需要等到IO事件完成。当操作的IO事件完成后,给我们的应用程序一个通知就OK
同步方式:
1) 阻塞到IO事件, 阻塞read/write ,完全不能做其他的事情
2) IO事件的轮询 -- 多路复用技术(selector模式),阻塞的是selector的轮询线程,不是所有的IO线程
2、Java IO模型
BIO:JDK1.4版本以前我们使用都是BIO,阻塞IO
NIO:JDK1.4版本之后 引入Linux Selector模式的多路复用技术,实现IO事件的轮询,完成同步非阻塞方式
现在主流网络通信模式 优秀框架有mina2,netty5(主流)
AIO:真正实现了异步IO,使用linux epoll模式
3、NIO的原理
通过selector(选择器)就相当于管家,管理所有的IO
Connection accept 客户端和服务端的读写---IO事件
selector(选择器)如何进行管理IO事件
当IO事件注册给我们的选择器的时候, 选择器会给他们分配一个key(可以简单的理解为一个标记)当IO事件完成通过key值来找到相应的通道,然后通过通道发送数据和接收数据操作
数据缓冲区:类似容器。
通过ByteBuffer,提供很多读写的方法 put(), get()
读和写是直接放到缓冲区Buffer中去的,客户端的属于用户的缓冲区并不属于内核的缓冲区。
属性:
Capacity 容量: 能够容纳元素的最大量,一旦设定是无法更改的。
Limit 上界,缓冲区的第一个不能被读或者写的元素
Position 位置: 自动有Buffer的put()或者get()方法进行更新的。
Mark 标记: 标记位置
四个属性的关系: 0=<mark<=positon<=limit<=capacity
服务端:ServerSocketChannel
客户端:SocketChannel
选择器:Selector selector=Select.open(); 这样就打开了我们选择器
通道和缓冲区之间是可以相互读和写的,Buffer的缓冲区既可以读又可以写,所以可以说是多
路复用,由缓冲区来提供读和写。
4、SelectionKey
可以通过它来判断IO事件是否已经就绪
key.isAccptable(): 是否可以接受客户端的连接
key.isConnectionable(): 是否可以连接服务器端
key.isReadable(): 缓冲区是否可读
key.isWriteable(): 缓冲区是否可写
SelectionKey key = Selector.selectKeys();
5、如何注册
channel.regist(Selector,SelectionKey.OP_Write);
channel.regist(Selector,SelectionKey.OP_Read);
channel.regist(Selector,SelectionKey.OP_Connect);
channel.regist(Selector,SelectionKey.OP_Accept);
6、缓冲区是如何进行工作的
这时涉及到用户空间(jvm),和内核空间(操作系统),数据最终是在磁盘中保存,一般永久的数
据会在磁盘上读取,磁盘的数据是存储在散区的。磁盘控制器是通过硬件接口与磁盘交互的。
DMA:直接内存访问,把磁盘中的数据刷到内核的缓冲区中的。然后通过用户中的线程的read方法将内核中缓冲区的数据读取到用户的缓冲区去。
上面的是IO中的缓存区的运行原理,有可能当DMA没有把数据全部刷新到内核的缓存区时,这时的read的操作将会阻塞。
在NIO中,不会阻塞线程,而是阻塞selector。selector看能否从缓存区中取全部的数据了,没有就等待返回继续读,直到全部读完。这个read已经发散了三次了。不会一直等的,这也是NIO优化的地方。
用户空间不能直接操作内核空间,在写代码的时候是都操作的,这就涉及到了内存。
内存里面有两个地址,使用了用户的虚拟映射和内核的虚拟映射,用的是同一个虚拟映射空间,才可以操作的。在计算机中有虚拟内存。类似于docker-v 挂载。操作内存的中缓存空间就是操作内核中的缓存区间。
7、AIO原理分析
服务端:AsynchronousServerSocketChannel
客户端:AsynchronousSocketChannel
用户处理器:CompletionHandler 接口,这个接口实现应用程序向操作系统发起IO请求,当完成IO操作通知处理具体逻辑,否则做自己该做的事情,使用complete,failed方法。