摘自《Netty权威指南》
Java NIO是 JDK1.4 引入的,弥补了原来BIO的不足
概念
缓冲区 Buffer
在 NIO 库中,所有数据都是用缓冲区处理的,任何时候访问 NIO 中的数据,都是通过缓冲区进行操作的。
最常用的缓冲区是 ByteBuffer
,提供了一组功能用于操作byte数组,还有其它的一些缓冲区,关系图如下:
通道 Channel
Channel 是一个通道,可以通过它读取和写入数据,与流的不同之处在于通道是双向的(可以读、写或者同时读写),流只是一个方向上移动(流必须是 InputStream
或 OutputStream
的子类)
因为 Channel 是全双工的,可以比流更好地映射操作系统地API,特别是在 UNIX 网络编程模型中,底层操作系统地通道都是全双工的,同时支持读写操作。其继承关系如下:
多路复用器 Selector
多路复用器提供选择已经就绪的任务的能力,它会不断地轮询注册在其上的 Channel,如果某个 Channel 上面有新的 TCP 连接接入、读和写事件,那么这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪的 Channel 的集合,进行后续的IO操作
一个 Selector 可以同时轮询多个 Channel,由于 JDK 使用了 epoll() 代替了传统的 select 实现,所以没有最大连接句柄限制
详解
服务端
NIO服务端序列图:
将之前的 TimeServer 改造成 NIO 模式:
1 | public class MultiplexerTimeServer implements Runnable { |
客户端
NIO客户端序列图:
将之前的 TimeClient 改造成 NIO 模式:
1 | public class MultiplexerTimeClient implements Runnable { |
总结
通过上述例子,NIO编程难度要比BIO大很多(这里并没有考虑”半包读”和”半包写”),其优点:
- 客户端发起的连接操作是异步的,可以通过在 selector 上注册 OP_CONNECT 等待后续结果
- SocketChannel 的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样IO线程可以处理其它的链路
- 线程模型优化,JDK的selector在Linux等主流操作系统上通过epoll实现,没有连接句柄的限制,意味着一个selector线程可以同时处理成千上万个客户端连接,而性能不会线性下降,适合做高性能、高负载的网络服务器