diff --git a/README.md b/README.md index f1fe19a..c95a6ca 100644 --- a/README.md +++ b/README.md @@ -127,32 +127,29 @@ ## Netty ### 网络 IO 技术基础 - * [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md) * [IO模型](docs/Netty/IOTechnologyBase/IO模型.md) * [四种IO编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md) -### Netty 粘拆包及解决方案 +### JDK1.8 NIO包 核心组件源码剖析 +* [Selector、SelectionKey及Channel组件](docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md) +### Netty 粘拆包及解决方案 * [TCP粘拆包问题及Netty中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md) ### Netty 编解码 - * [Java序列化缺点与主流编解码框架](docs/Netty/Netty编解码/Java序列化缺点与主流编解码框架.md) ### Netty 多协议开发 - * [基于HTTP协议的Netty开发](docs/Netty/Netty多协议开发/基于HTTP协议的Netty开发.md) * [基于WebSocket协议的Netty开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md) * [基于自定义协议的Netty开发](docs/Netty/Netty多协议开发/基于自定义协议的Netty开发.md) ### 基于 Netty 开发服务端及客户端 - * [基于Netty的服务端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发.md) * [基于Netty的客户端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发.md) ### Netty 主要组件的源码分析 - * [ByteBuf组件](docs/Netty/Netty主要组件源码分析/ByteBuf组件.md) * [Channel组件 和 Unsafe组件](docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md) * [ChannelPipeline 和 ChannelHandler组件](docs/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件.md) @@ -160,7 +157,6 @@ * [Future 和 Promise组件](docs/Netty/Netty主要组件源码分析/Future和Promise组件.md) ### Netty 高级特性 - * [Netty 架构设计](docs/Netty/AdvancedFeaturesOfNetty/Netty架构设计.md) * [Netty 高性能之道](docs/Netty/AdvancedFeaturesOfNetty/Netty高性能之道.md) diff --git a/docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md b/docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md new file mode 100644 index 0000000..c99a8f5 --- /dev/null +++ b/docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md @@ -0,0 +1,298 @@ +Selector、SelectionKey和Channel 这三个组件构成了Java nio包的核心,也是Reactor模型在代码层面的体现。Selector能让单线程同时处理多个客户端Channel,非常适用于高并发,传输数据量较小的场景。要使用Selector,首先要将对应的Channel及IO事件(读、写、连接)注册到Selector,注册后会产生一个SelectionKey对象,用于关联Selector和Channel,及后续的IO事件处理。这三者的关系如下图所示。 + +![在这里插入图片描述](../../../images/Netty/Selector和SelectionKey和Channel关系图.png) + +对nio编程不熟的同学可以搜索一些简单的demo跑一下,下面 我们直接进入源码,窥探一些nio的奥秘。 +### Selector +其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有事件,再找一些具体的实现类深入分析一下。 +```java +public abstract class Selector implements Closeable { + + protected Selector() { } + + /** + * 获取一个 Selector对象,具体实现依赖于底层操作系统 + */ + public static Selector open() throws IOException { + return SelectorProvider.provider().openSelector(); + } + + /** + * 判断该 Selector 是否已开启 + */ + public abstract boolean isOpen(); + + /** + * 当前所有向Selector注册的Channel 所对应的SelectionKey的集合 + */ + public abstract Set keys(); + + /** + * 相关事件已经被 Selector 捕获的 SelectionKey的集合 + */ + public abstract Set selectedKeys(); + + /** + * 阻塞到至少有一个通道在你注册的事件上就绪了 + */ + public abstract int select() throws IOException; + + /** + * 和select()一样,除了最长会阻塞timeout毫秒 + */ + public abstract int select(long timeout) throws IOException; + + /** + * 此方法执行非阻塞的选择操作,如果自从上一次选择操作后, + * 没有通道变成可选择的,则此方法直接返回 0 + */ + public abstract int selectNow() throws IOException; + + /** + * 用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效 + * 通道本身并不会关闭 + */ + public abstract void close() throws IOException; +} +``` + +### SelectionKey +表示 SelectableChannel 在 Selector 中的注册的标记 / 句柄。 +```java +public abstract class SelectionKey { + + protected SelectionKey() { } + + + // -- Channel and selector operations -- + + /** + * 获取该 SelectionKey 对应的Channel,Channel注册到Selector时会产生该 SelectionKey对象 + */ + public abstract SelectableChannel channel(); + + /** + * 获取该 SelectionKey 对应的 Selector + */ + public abstract Selector selector(); + + /** + * 该 SelectionKey 是否是有效的 + */ + public abstract boolean isValid(); + + // ------ Operation-set accessors ------ + + /** + * 获取该 SelectionKey 的兴趣事件 (既 SelectionKey 的4个 事件静态常量) + */ + public abstract int interestOps(); + + /** + * 设置该 SelectionKey 的兴趣事件 + */ + public abstract SelectionKey interestOps(int ops); + + /** + * 获取该 SelectionKey 的已操作集 + */ + public abstract int readyOps(); + + + // ------ Operation bits and bit-testing convenience methods ------ + + /** + * channel中的数据是否已经可以读取 + */ + public static final int OP_READ = 1 << 0; + + /** + * channel是否可以开始写入数据 + */ + public static final int OP_WRITE = 1 << 2; + + /** + * channel是否已经建立连接 + */ + public static final int OP_CONNECT = 1 << 3; + + /** + * ServerSocketChannel 是否可以与客户端建立连接 + */ + public static final int OP_ACCEPT = 1 << 4; + + /** + * channel是否可读 + */ + public final boolean isReadable() { + return (readyOps() & OP_READ) != 0; + } + + /** + * channel是否可写 + */ + public final boolean isWritable() { + return (readyOps() & OP_WRITE) != 0; + } + + /** + * channel是否建立连接 + */ + public final boolean isConnectable() { + return (readyOps() & OP_CONNECT) != 0; + } + + /** + * ServerSocketChannel是否可与客户端channel建立连接 + */ + public final boolean isAcceptable() { + return (readyOps() & OP_ACCEPT) != 0; + } +} +``` +### Channel组件 +平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel,而将 Channel 与 Selecor 关联到一起的核心API则定义在它们的公共父类SelectableChannel中,整个Channel组件的核心类图如下所示。 + +![在这里插入图片描述](../../../images/Netty/Channel组件.png) + +#### SelectableChannel +```java +public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { + + protected SelectableChannel() { } + + /** + * 当前channel是否注册到了某个selector上,新创建的channel都是未注册状态 + */ + public abstract boolean isRegistered(); + + /** + * 根据给定的 Selector,获取本channel注册上去的 SelectionKey + */ + public abstract SelectionKey keyFor(Selector sel); + + /** + * 将当前channel及关注的事件,注册到Selector上,返回一个 SelectionKey + */ + public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException { + return register(sel, ops, null); + } + + public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException; + + /** + * 设置该channel的阻塞模式,默认为 true阻塞 + */ + public abstract SelectableChannel configureBlocking(boolean block) throws IOException; + + /** + * 是否为阻塞IO模式 + */ + public abstract boolean isBlocking(); +} +``` + +#### ServerSocketChannel +相当于 BIO 中的 ServerSocket,主要用于服务端与客户端建立连接通信的channel。 +```java +public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel { + + protected ServerSocketChannel(SelectorProvider provider) { + super(provider); + } + + /** + * 获取一个 ServerSocketChannel实例,具体实现依赖底层操作系统 + */ + public static ServerSocketChannel open() throws IOException { + return SelectorProvider.provider().openServerSocketChannel(); + } + + // -- ServerSocket-specific operations -- + + /** + * 绑定ip地址及要监听的端口 + */ + public final ServerSocketChannel bind(SocketAddress local) throws IOException { + return bind(local, 0); + } + + public abstract ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException; + + /** + * 与一个客户端channel建立连接,返回该客户端的存根 SocketChannel + */ + public abstract SocketChannel accept() throws IOException; +} +``` +#### SocketChannel +相当于 BIO 中的 Socket,主要用于通信双方的读写操作。 +```java +public abstract class SocketChannel extends AbstractSelectableChannel + implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel { + + protected SocketChannel(SelectorProvider provider) { + super(provider); + } + + /** + * 根据 SocketAddress 获取一个 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; + } + + public static SocketChannel open() throws IOException { + return SelectorProvider.provider().openSocketChannel(); + } + + // -- Socket-specific operations -- + + /** + * 绑定要连接的远程服务的ip及端口 + */ + @Override + public abstract SocketChannel bind(SocketAddress local) throws IOException; + + /** + * 该channel与服务端是否已连接 + */ + public abstract boolean isConnected(); + + // -- ByteChannel operations -- + + /** + * 将 channel 中的数据读到 ByteBuffer + */ + public abstract int read(ByteBuffer dst) throws IOException; + + public final long read(ByteBuffer[] dsts) throws IOException { + return read(dsts, 0, dsts.length); + } + + public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException; + + /** + * 将 ByteBuffer 中的数据写到 channel + */ + public abstract int write(ByteBuffer src) throws IOException; + + public final long write(ByteBuffer[] srcs) throws IOException { + return write(srcs, 0, srcs.length); + } + + public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException; +} +``` \ No newline at end of file diff --git a/images/Netty/Channel组件.png b/images/Netty/Channel组件.png new file mode 100644 index 0000000..a49c24f Binary files /dev/null and b/images/Netty/Channel组件.png differ diff --git a/images/Netty/Selector和SelectionKey和Channel关系图.png b/images/Netty/Selector和SelectionKey和Channel关系图.png new file mode 100644 index 0000000..2bcd498 Binary files /dev/null and b/images/Netty/Selector和SelectionKey和Channel关系图.png differ