JAVA NIO
NIO库是在JDK 1.4中引入的。JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO2.0。在这篇文章中,我们只介绍JDK中的NIO2。NETTY其实就是基于NIO2(注意有部分是自己实现的,比如说对epoll的使用)。https://stackoverflow.com/questions/23465401/why-native-epoll-support-is-introduced-in-netty
以下的NIO均指NIO2。
JAVA NIO会根据操作系统的种类和版本决定使用哪一种系统调用来实现NIO。对于Linux,常见的有epoll,poll,select。在Linux 2.6+版本,Java NIO采用的epoll(即EPollSelectorImpl类),对于2.4.x的,则使用poll(即PollSelectorImpl类)。
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
使用NIO一定要注意NIO臭名昭著的epoll空轮训BUG。selector的轮训(select)结果为空的情况下,则就相当于while循环满载,会迅速耗尽CPU资源。Netty对这个BUG进行了简单解决,就是对某段时间内的空轮训进行计数,如果超过设定值则会重建select,将原有的Channel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。
https://www.cnblogs.com/JAYIT/p/8241634.html
类图
通过Channel我们可以实现零拷贝,下图中1,3是DMA传输。2就是MMAP。这是使用native方法
sun.nio.ch.FileChannelImpl#transferTo0
DirectByteBuffer内部采用unsafe类进行分配内存
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
基本用法
System.out.println("Listening on port " + port);
ServerSocketChannel serverChannel = ServerSocketChannel.open();// 打开一个未绑定的serversocketchannel
ServerSocket serverSocket = serverChannel.socket();// 得到一个ServerSocket去和它绑定
Selector selector = Selector.open();// 创建一个Selector供下面使用
serverSocket.bind(new InetSocketAddress(port));//设置server channel将会监听的端口
serverChannel.configureBlocking(false);//设置非阻塞模式
serverChannel.register(selector, SelectionKey.OP_ACCEPT);//将ServerSocketChannel注册到Selector
while (true)
{
// This may block for a long time. Upon returning, the
// selected set contains keys of the ready channels.
int n = selector.select();
if (n == 0)
{
continue; // nothing to do
}
java.util.Iterator<SelectionKey> it = selector.selectedKeys().iterator();// Get an iterator over the set of selected keys
//在被选择的set中遍历全部的key
while (it.hasNext())
{
SelectionKey key = (SelectionKey) it.next();
// 判断是否是一个连接到来
if (key.isAcceptable())
{
ServerSocketChannel server =(ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
registerChannel(selector, channel,SelectionKey.OP_READ);//注册读事件
sayHello(channel);//对连接进行处理
}
//判断这个channel上是否有数据要读
if (key.isReadable())
{
readDataFromSocket(key);
}
//从selected set中移除这个key,因为它已经被处理过了
it.remove();
}
}
AIO
其实就是返回了一个future。
//AsynchronousServerSocketChannel.java
public abstract Future<AsynchronousSocketChannel> accept();
AsynchronousSocketChannel.java
@Override
public abstract Future<Integer> read(ByteBuffer dst);
@Override
public abstract Future<Integer> write(ByteBuffer src);
和Netty对比
一般认为,Netty比NIO2要强。spark/kafka使用的是NIO,但是dubbo使用的是netty。老师讲,二者在原理上并没有什么本质区别,但是只不过Netty BUG更少,功能更稳定,使用更简单。个人觉得,国外框架代码不愿意使用netty,主要是尽量减少对其他框架的依赖的考虑。
典型使用
我们看下kafka-client对NIO selector的使用好了
//对server进行连接
public void connect(String id, InetSocketAddress address, int sendBufferSize, int receiveBufferSize) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
...
//注册到nioSelector上
SelectionKey key = socketChannel.register(nioSelector, SelectionKey.OP_CONNECT);