本篇将用NIO2实现一个简单的IO多路复用程序并分析它,其中涉及到了ServerSocketChannel的configureBlocking()、Selector的select()、ServerSocketChannel的accept()等方法的理解。
单线程模型实例
先放上代码:
|
|
命令行执行如下代码:
|
|
输入想要打印的内容会出现echo效果。
这段代码有三个比较重要的节点,分别是configureBlocking()的调用、selector.select()的调用以及ssc.accept()的调用,后续讲对其进行展开分析。
configureBlocking()
所谓非阻塞同步的非阻塞,说的就是这里。在Stackoverflow: Socket Channel configureBlocking to false下的答案给出了该方法的使用说明,通道默认是阻塞模式(也就是true),调用每个I/O操作都将引起通道的阻塞,直到操作完成。在register()之前应该设置其为false,如果取消了注册,通道可能不会返回阻塞模式。
selector.select()
接下来看看select()调用的底层脉络。
Selector是抽象类,代码中是通过Selector.open()来返回具体实例的:
|
|
查看provider()代码:
|
|
当provider为空则通过provider = sun.nio.ch.DefaultSelectorProvider.create();
产生实例。继续追踪有:
|
|
以及:
|
|
因为本实验环境为MacOX,所以Selector.open()返回的对象是一个KQueueSelectorImpl,它是一个Selector实现类。该类方法select()的实现在抽象父类SelectorImpl中,代码如下:
|
|
继续查看lockAndDoSelect()有:
|
|
这个doSelect()经由KQueueSelectorImpl实现:
|
|
poll()源码为:
|
|
kevent0()是一个native方法:private native int kevent0(int kq, long keventAddress, int keventCount, long timeout);
。
本地方法的实现:
|
|
结合之前本人关于Kqueue的文章,就不难理解这段代码的原理,select()底层还是调用的kevent(),所以Java NIO.2的相关方法是对底层的更高级抽象。注释表明,Java的timeout参数小于0时,相当于给kevent()的timeout参数传入NULL,表示一直等待。
ssc.accept()
ServerSocketChannel是一个可以监听新的TCP连接的通道。看看该通道accept()的注释:
Accepts a connection made to this channel’s socket.
If this channel is in non-blocking mode then this method will
immediately return null if there are no pending connections.
Otherwise it will block indefinitely until a new connection is available
or an I/O error occurs.The socket channel returned by this method, if any, will be in
blocking mode regardless of the blocking mode of this channel.
也就是说,在非阻塞模式,如果没有连接将立即返回,在阻塞模式将一直阻塞等待新的可用连接或者一个I/O错误的发生。
返回的SocketChannel通道将为阻塞模式,无论ServerSocketChannel方法调用之前是什么模式。
小结
本文分析的多路复用程序可以看作是Epoll、Kqueue在Java层面的实现,Java将底层的系统调用进行了很好的封装。本篇文章为后续Netty框架的学习进一步打下了基础。
参考
Socket Channel configureBlocking to false
Stackoverflow: What is “jobject thiz” in JNI and what is it used for?