Channel
Channel
是 Java NIO 的一个基本构造,它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或一个能够执行一个或多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作。
目前,可以把 Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。
回调
Netty 在内部使用了回调来处理事件,当一个回调被触发时,相关的事件可以被一个 ChannelHandler
接口的实现处理。
当一个新的连接被建立时, ChannelHandler
的 channelActive()
回调方法将会被调用。例如:
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
}
}
Future
Future 提供了另一种在操作完成时通知应用程序的方式,这个对象可以看作是一个异步操作的结果的占位符。它将在未来的某个时刻完成,并提供对其结果的访问。
JDK 预置了 java.util.concurrent.Future
接口,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常频繁的,所以 Netty 提供了它自己的实现 ChannelFuture
,用于在执行异步操作的时候使用。
ChannelFuture 提供了几种额外的方法,这些方法使得我们能够注册一个或多个 ChannelFutureListener
实例。监听器的回调方法 operationComplete()
将会在对应的操作完成时被调用。然后监听器可以判断该操作是否出错,如果是出错了,我们可以检索产生的 Throwable 。简而言之,由 ChannelFutureListener 提供的通知机制消除了手动检查对应的操作是否完成的必要性。
每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture ,也就是说,它们都不会阻塞,所以说 Netty 是异步(非阻塞)和事件驱动的。异步地建立连接:
Channel channel = ...;
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1", 25));
注册一个 ChannelFutureListener 到 connect()
方法返回的 ChannelFuture 上。当监听器被通知连接已经建立(回调 operationComplete()
方法)的时候,要检查对应的状态。如果成功,则将数据写到该 Channel ,否则检索 Throwable 。
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
ByteBuf buffer = Unpooled.copiedBuffer("Hello", Charset.defaultCharset());
ChannelFuture wf = future.channel().writeAndFlush(buffer);
// ...
} else {
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
如果 ChannelFutureListener 添加到 ChannelFuture 的时候, ChannelFuture 已经完成,那么该 ChannelFutureListener 将会被直接地通知。
需要注意的是,对错误的处理完全取决于你,当然也包括目前任何对于特定类型的错误加以的限制。例如,如果连接失败,你可以尝试重新连接或者建立一个到另一个远程节点的连接。
如果你把 ChannelFutureListener 看作是回调的一个更加精细的版本也是可以的。事实上,回调和 Future 是互相补充的机制,它们互相结合,构成了 Netty 本身的关键构件之一。
事件和 ChannelHandler
Netty 使用不同的事件来通知我们状态的改变,这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:记录日志、数据转换、流控制、应用程序逻辑。
Netty 是一个网络编程框架,所以事件是按照它们与入站或入站数据流的相关性进行分类的。可能由入站数据或相关的状态更改而触发的事件包括:连接已被激活或者连接失活、数据读取、用户事件、错误事件。出站事件是未来将会触发的某个动作的操作结果,这些动作包括:打开或者关闭到远程节点的连接、将数据写到或者冲刷到套接字。
每个事件都可以被分给 ChannelHandler
类中的某个用户实现的方法。这是一个很好的将事件驱动范式直接转换为应用程序构件块的例子。
下图展示了一个事件是如何被一个这样的 ChannelHandler 链处理的:
Netty 的 ChannelHandler 为处理器提供了基本的抽象,目前你可以认为每个 ChannelHandler 的实例都类似于一种为了响应特定事件而被执行的回调。
总结
Netty 的异步编程模型是建立在 Future 和回调的概念之上的,而将事件派发到 ChannelHandler 的方法则发生在更深的层次上。拦截操作以及高速地转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的 Future 。
Netty 通过触发事件将 Selector(选择器) 从应用程序中抽象出来,消除了所有本来将需要手动编写的派发代码。在内部,将会为每个 Channel 分配一个 EventLoop ,用于处理这个 Channel 的所有 I/O 事件,包括:注册感兴趣的事件、将事件派发给 ChannelHandler 、安排进一步的动作。