在从NIO编程到Netty的使用中,我们简单介绍了一下Netty,并写了一个很简单的小例子,这里我们就来详细介绍一个之前例子中用到的一些Netty的组件。
基本的I/O 操作bind()、connect()、read()和write()
依赖于底层网络传输所提供的原语。在基于Java 的网络编程中,其基本的构造是class Socket。Netty 的Channel 接口所提供的API,被用于所有的I/O 操作。大大地降低了直接使用Socket 类的复杂性。此外,Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根。
每个Channel 都将会被分配一个ChannelPipeline 和ChannelConfig。ChannelConfig 包含了该Channel 的所有配置设置,并且支持热更新。
由于Channel 是独一无二的,所以为了保证顺序将Channel 声明为Comparable接口的一个子接口。因此,如果两个不同的Channel 实例都返回了相同的散列码,那么AbstractChannel 中的compareTo()方法的实现将会抛出一个Error。
ChannelUnregistered
Channel 已经被创建,但还未注册到EventLoop
ChannelRegistered
Channel 已经被注册到了EventLoop
ChannelActive
Channel 处于活动状态(已经连接到它的远程节点),可以接收和发送数据了
ChannelInactive
Channel 没有连接到远程节点
eventLoop()
返回分配给Channel 的EventLooppipeline()
返回分配给Channel 的ChannelPipelineisActive()
如果Channel 是活动的,则返回true。活动的意义可能依赖于底层的传输。例如,一个Socket 传输一旦连接到了远程节点便是活动的,而一个Datagram 传输一旦被打开便是活动的。localAddress()
返回本地的SokcetAddressremoteAddress()
返回远程的SocketAddresswrite()
将数据写到远程节点。这个数据将被传递给ChannelPipeline,并且排队直到它被冲刷flush()
将之前已写的数据冲刷到底层传输,如一个SocketwriteAndFlush()
一个简便的方法,等同于调用write()并接着调用flush()其中最后三个方法write()、flush()、writerAndFlush()
是在其父接口中定义的
EventLoop定义了Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。io.netty.util.concurrent 包构建在JDK 的java.util.concurrent 包上。一个EventLoop 将由一个永远都不会改变的Thread 驱动,同时任务(Runnable 或者Callable)可以直接提交给EventLoop 实现,以立即执行或者调度执行。
根据配置和可用核心的不同,可能会创建多个EventLoop 实例用以优化资源的使用,并且单个EventLoop 可能会被指派用于服务多个Channel,如下
异步传输的实现只使用了少量的EventLoop(以及和它们相关联的Thread),而且在上述的线程模型中,它们可能会被多个Channel 所共享。这使得可以通过尽可能少量的Thread 来支撑大量的Channel,而不是每个Channel 分配一个Thread。
EventLoopGroup 负责为每个新创建的Channel 分配一个EventLoop。在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的EventLoop可能会被分配给多个Channel。一旦一个Channel 被分配给一个EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的Thread)。
另外需要注意的是,EventLoop的分配方式对ThreadLocal的使用的影响。因为一个EventLoop 通常会被用于支撑多个Channel,所以对于所有相关联的Channel 来说,ThreadLocal 都将是一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。但是在一些无状态的上下文中,它仍然可以被用于在多个Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件。
在内部,当提交任务到如果(当前)调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。
Netty的EventLoop在继承了ScheduledExecutorService的同时,只定义了一个方法parent()
。在Netty 4中,所有的I/O操作和事件都由已经被分配给了EventLoop的那个Thread来处理。
从上述的类图中发现其实现了ScheduledExecutorService接口,即可以调度一个任务以便稍后(延迟)执行或者周期性地执行。例如,你可能想要注册一个在客户端已经连接了5 分钟之后触发的任务。一个常见的方法是,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该Channel了。
Netty中所有的I/O操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此Netty提供了ChannelFuture接口,其addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。可以将ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。