Nio学习简单理解


背景

最近一直在研究netty,netty是一个提供异步事件驱动的网络应用框架,用以开发高并发高可靠的网络服务器和客户端程序。我们可以看到一些优秀的开源框架也采用了netty作为底层的基础通信组件,像淘宝的dubbo和消息中间件RocketMQ。因为Netty是基于JAVA NIO类库开发的,所以我们必须要先了解nio。

NIO

nio是java 1.4版本引入的新的io api,可以替代原来标准的io api。首先介绍下nio的组成部分,它主要有一下3个部分组成:channel,buffer,selector,下面将分别介绍各自疼点。

channel

所有io在nio中都从一个channel开始,数据可以从channel读取到buffer中,也可以从buffer写入到channel中。有点和原来的标准io中的流类似,但又有明显的区别。

像上面所说的channel是双向的,但是流的读写通常是单向的
通道可以异步的读写
通道的数据总是总是要先读到一个buffer,或者从一个buffer中写入。
Channel的实现有以下几种。

FileChannel:从文件中读写数据。
DatagramChannel:能通过UDP读写网络中的数据。
SocketChannel:能通过TCP读写网络中的数据。
ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

buffer

buffer本质上是一块可以写入数据,并且从中可以读取数据的内存,这块内存被封装成了nio buffer对象。为了了解buffer的工作原理,必须先要了解一下3个属性:capacity,limit,position。
capacity:作为一个内存块,buffer大小叫capacity。
position,limit:这两个在读和写模式下的意义不同。写模式:position初始为0,limit等于capacity-1,没写一个数据,position加1,最多写capacity个亦即position=limit时。调用buffer.flip()可以将写模式切换到读模式,此时position=0,limit被设置成之前写模式最后position的值,我们可以读取从position到limit之间的数据。
buffer主要有以下几个方法,下面分别介绍。

flip():上面已经说过
clear():“清空”缓冲区的数据,以便重新写入。这里不是真的清空内存,是把positon设置为0,limit设置为capacity-1.
compact():和clear类似也是清空数据,只不过是把缓冲区中还未读完的数据移到缓冲区开头。也就是说limit还是capacity-1,position移到未读完数据后一个。
rewind():postiton设置为0,limit位置不变,让缓冲区的数据可以被重新读。
mark()和reset()配合使用,mark给postion当前位置做个标记,嗲用reset后,postion回到mark的位置。

selector

选择器我认为是nio中最重要的一个部分,nio的异步事件驱动就是靠这个selector来实现的。我们可以只需要启动一个线程,创建一个selector,将多个channel注册到selector上面,然后关注每个channel上我们感兴趣的事件(connect,accept,read,write)。这中方式和传统相比,显然有很大的优势。
传统方式,服务端启动一个serversocket,监听一个端口,accept一个客户端连接后,然后通过线程池创建一个线程去处理这个请求。如果这个这个请求要处理io,那么这个线程可能就会阻塞很长时间,造成资源浪费。另外线程如果太多,频繁的线程切换也会造成不小的开销。而如果采用nio,可以基于reactor模型,用一个selector管理多个通道,这就是io多路复用,另外如果select到一个感兴趣的事件,比如一个通道的read就绪,那么然后操作io就可以直接读了(因为之前我们已经知道读就绪了),这就是非阻塞。总结一下由于采用了io多路复用I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上
下面贴一段代码演示一下selector的用法。

1
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}

参考

这篇文章对同步异步阻塞与非阻塞讲解的很好
java nio的另一篇同步异步阻塞非阻塞的介绍
这篇对nio讲解的很透彻


文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
再见,陆金所 再见,陆金所
今天刚拿到退工单,陆金所已经成为过去了。从2月5号提出辞职,到今天,差不多一个月的时间。在这一个月里,因为确定要走,所以工作上的事情不是很多。但也并没有闲着,除了每天回去接着敲代码看看书,期间也想了很多事情。有必要总结一下这段工作经历。
2016-03-02
下一篇 
第一篇博客 第一篇博客
We如你所见,我的个人博客终于搞定了。前前后后大概花了一个多月,经历了种种困难,还好坚持下来了。现在网站既然搞定了,就开始写自己的第一篇博客。主要讲述下创建个人博客的原因、经过以及今后的打算。 原因大学毕业找工作经历其实从报考交大研究生,由
2016-02-20
  目录