通过源码理解反应器模式在javaNIO的实现
本文主要分析Serversocketchannel以及socketChannel两个类
反应器模式主要分为三步:
注册 channel->selector
轮询 selector->reactor
分发 reactor->handler
大致步骤就是将通道注册到选择器,选择器轮询各个reactor,如果接收到消息,则分发到对应的handler处理。
这边就涉及到四个对象,channel,selector,reactor,handler。
channel
首先来看channel类:
public interface Channel extends Closeable {
public boolean isOpen();
a
public void close() throws IOException;
}
就两个方法,判断是否开着,以及关闭。
其还继承了一个Closeable类:
并捕获可能产生的IO异常。
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
其还继承了一个AutoCloseable接口:
public interface AutoCloseable {
void close() throws Exception;
}
里面就定义了一个close方法。
接下来看Channel的几个继承类:
有很多,先看下NetworkChannel接口
其定义了几个方法:
public interface NetworkChannel
extends Channel
{
//将某个本地地址绑定到socket
NetworkChannel bind(SocketAddress local) throws IOException;
//返回绑定到socket的本地地址
SocketAddress getLocalAddress() throws IOException;
//设置socket的选项
<T> NetworkChannel setOption(SocketOption<T> name, T value) throws IOException;
//获得socket的选项
<T> T getOption(SocketOption<T> name) throws IOException;
//获得这个通道支持的socket选项
Set<SocketOption<?>> supportedOptions();
}
再看主要的子类ServerSocketChannel,其是继承NetworkChannel,间接继承Channel。同时又继承了AbstractSelectableChannel。
AbstractSelectableChannel抽象类定义了channel注册/取消注册/关闭的处理,这边涉及到selector,先放着。
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel
{
//ServerSocketChannel的初始化,由SelectorProvider创建
protected ServerSocketChannel(SelectorProvider provider) {
super(provider);
}
//由SelectorProvider初始化并开启channel
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
//返回通道可用的操作,因为Server-socket channels只支持接收新连接
//所以只返回ACCEPT操作。
public final int validOps() {
return SelectionKey.OP_ACCEPT;
}
// -- ServerSocket-specific operations --
//绑定,具体方法看下个
public final ServerSocketChannel bind(SocketAddress local)
throws IOException
{
return bind(local, 0);
}
//将socket和本地地址绑定,若local传的是null,则自动随机绑定,
//backlog的值是等待连接的最大数,其具体语意在继承类中定义
public abstract ServerSocketChannel bind(SocketAddress local, int backlog)
throws IOException;
public abstract <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
throws IOException;
//创建一个和channel绑定的server socket
public abstract ServerSocket socket();
//获得客户端发起的新连接
//如果当前channel处于non-blocking mode,或者没有等待中的连接,
//则返回空。不然它会一直阻塞直到新连接空闲或者产生io异常。
public abstract SocketChannel accept() throws IOException;
//获得当前channel绑定的本地地址,如果有security manager设置,
//且为允许,则返回本地端口号,否则返回loopback address(不知道是啥)
@Override
public abstract SocketAddress getLocalAddress() throws IOException;
}
(以上都是单纯地理解了一下注释,到目前为止,还是没有一个清晰的概念)
这个是ServerSocketChannel,接下来看一下SocketChannel
(可以理解为一个是服务端,一个是客户端)
类上的注释很长,但是感觉很有意思,自己翻译一下:
一个可以被选择器控制(selectable)提供给面向流的的sockets的channel
socket channel必须被其类中的open创建,不能创建给一个随意的,已经存在的socket。
新创建的socket channel是开启的但没有被连接。对一个没有被连接的channel调用其IO操作会抛出NotYetConnectedException。
其可以被自己类内部的connect方法连接。一旦连接,socket channel就会保持连接直到被关闭。可以通过isConnected方法判断其是否已连接。
socket channels支持NIO连接。一个socket channel在被创建之后通过connect和finishConnect方法建立与远端socket的连接。
可以通过isConnectionPending判断是否在连接中。
socket channels允许被匿名的和channel类中的异步close类似的方法关闭(shutdown)。
如果socket的一端还在执行读(read)操作,另一端被一个线程关闭,则被阻塞的线程结束read并返回-1.
如果socket的一端还在执行写(write)操作,另一端被一个线程关闭,则抛出AsynchronousCloseException异常。
接下来是对socket options的一些定义声明
。。。。。。
socket channels是线程安全的,其支持并发的读写。当然最多同时只能有一个线程在读或者写。其connect和finishconnect方法是互相同步的。在这两个方法执行过程中的读写初始化操作会被阻塞直到方法执行结束。
接下来看类内的方法:
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
{
//创建一个类的实例
protected SocketChannel(SelectorProvider provider) {
super(provider);
}
//开启一个socketChannel
public static SocketChannel open() throws IOException {
return SelectorProvider.provider().openSocketChannel();
}
//开启socketChannel并连接到远端地址
//必须连上了才返回
public static SocketChannel open(SocketAddress remote)
throws IOException
{
SocketChannel sc = open();
try {
sc.connect(remote);
} catch (Throwable x) {
try {
sc.close();
} catch (Throwable suppressed) {
x.addSuppressed(suppressed);
}
throw x;
}
assert sc.isConnected();
return sc;
}
//这边的validOps和serverSocketChannel的完全相反,
//socketChannel支持连接和读写
//这边涉及到selector的轮询,针对连接和读写操作,
//能够分发到对应的handler
public final int validOps() {
return (SelectionKey.OP_READ
| SelectionKey.OP_WRITE
| SelectionKey.OP_CONNECT);
}
// -- Socket-specific operations --
//将本地端口地址绑定到channel
@Override
public abstract SocketChannel bind(SocketAddress local)
throws IOException;
@Override
public abstract <T> SocketChannel setOption(SocketOption<T> name, T value)
throws IOException;
//在不关闭channel的情况下关闭read操作,之后的所有
//read操作直接返回-1。如果input写入端已经关闭了,那调用这个
//方法就没效果了。
public abstract SocketChannel shutdownInput() throws IOException;
//和上面的方法类似,只是改成了关闭write操作,区别是之后的write
//操作会直接抛出ClosedChannelException,如果输出端已经关闭,那调
//用这个方法也没效果
public abstract SocketChannel shutdownOutput() throws IOException;
//创建一个和该channel绑定的socket
public abstract Socket socket();
//判断是否已连接
public abstract boolean isConnected();
//判断是否正在连接中
public abstract boolean isConnectionPending();
//与远端建立连接
//如果该channel是非阻塞模式的,则初始化一个非阻塞的连接操作,
//如果该连接很快就建立了,且同时能发生一个本地连接(啥意思?)
//那么返回true。不然返回false且必须在之后调用finishConnect方法完成建立。
public abstract boolean connect(SocketAddress remote) throws IOException;
//如果连接已经被建立,那么该方法随时可能被调用。如果channel已连接
//,直接返回true,如果处于非阻塞模式且连接还未完成,返回false。
public abstract boolean finishConnect() throws IOException;
public abstract SocketAddress getRemoteAddress() throws IOException;
// -- ByteChannel operations --
接下来就是一些读写操作
public abstract int read(ByteBuffer dst) throws IOException;
public abstract long read(ByteBuffer[] dsts, int offset, int length)
throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public abstract int write(ByteBuffer src) throws IOException;
public abstract long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
public abstract SocketAddress getLocalAddress() throws IOException;
}
总结:
通过查看ServerSocketChannel和SocketChannel源码可以发现,首先都是基于NIO非阻塞模式来实现的。除此之外这边主要是定义了channel的初始化,与socket的绑定。在socketChannel中则是定义了与远端socket的连接。如果连接没有马上成功,还会后续调用finishConnect方法完成连接。并且在socketChannel中定义了基本的读写方法。ServerSocketChannel只是监听了客户端的连接。而在socketChannel中监听了连接以及读写操作。
接下来要先看看socketChannel具体connect的实现方式,以及两者对应的open方法,是如何通过SelectorProvider开启channel的(这就要先看看selector了)。