本章主要内容
- 引导客户端和服务器
- 从
Channel内引导客户端
- 添加
ChannelHandler
- 使用
ChannelOption和属性[1]
在深入地学习了ChannelPipeline、ChannelHandler和EventLoop之后,你接下来的问题可能是:“如何将这些部分组织起来,成为一个可实际运行的应用程序呢?”
答案是?“引导”(Bootstrapping)。到目前为止,我们对这个术语的使用还比较含糊,现在已经到了精确定义它的时候了。简单来说,引导一个应用程序是指对它进行配置,并使它运行起来的过程——尽管该过程的具体细节可能并不如它的定义那样简单,尤其是对于一个网络应用程序来说。
和它对应用程序体系架构的做法[2]一致,Netty处理引导的方式使你的应用程序[3]和网络层相隔离,无论它是客户端还是服务器。正如同你将要看到的,所有的框架组件都将会在后台结合在一起并且启用。引导是我们一直以来都在组装的完整拼图[4]中缺失的那一块。当你把它放到正确的位置上时,你的Netty应用程序就完整了。
8.1 Bootstrap类
引导类的层次结构包括一个抽象的父类和两个具体的引导子类,如图8-1所示。
图8-1 引导类的层次结构
相对于将具体的引导类分别看作用于服务器和客户端的引导来说,记住它们的本意是用来支撑不同的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父Channel的Channel来用于所有的网络交互。(正如同我们将要看到的,这也适用于无连接的传输协议,如UDP,因为它们并不是每个连接都需要一个单独的Channel。)
我们在前面的几章中学习的几个Netty组件都参与了引导的过程,而且其中一些在客户端和服务器都有用到。两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由Bootstrap或ServerBootstrap处理。
在本章中接下来的部分,我们将详细地探讨这两个类,首先从不那么复杂的Bootstrap类开始。
为什么引导类是Cloneable的
你有时可能会需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了Cloneable[5]。在一个已经配置完成的引导类实例上调用clone方法将返回另一个可以立即使用的引导类实例。
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者[6]将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一个典型的场景是——创建一个Channel以进行一次HTTP请求。
AbstractBootstrap类的完整声明是:
public abstract class AbstractBootstrap
<B extends AbstractBootstrap<B,C>,C extends Channel>
在这个签名中,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。
其子类的声明如下:
public class Bootstrap
extends AbstractBootstrap<Bootstrap,Channel>
和
public class ServerBootstrap
extends AbstractBootstrap<ServerBootstrap,ServerChannel>
8.2 引导客户端和无连接协议
Bootstrap类被用于客户端或者使用了无连接协议的应用程序中。表8-1提供了该类的一个概览,其中许多方法都继承自AbstractBootstrap类。
表8-1 Bootstrap类的API
名 称
|
描 述
|
Bootstrap group(EventLoopGroup)
设置用于处理Channel所有事件的EventLoopGroup
Bootstrap channel(
Class<? extends C>)
Bootstrap channelFactory(
ChannelFactory<? extends C>)
channel方法指定了Channel的实现类。如果该实现类没提供默认的构造函数[7],可以通过调用channel- Factory方法来指定一个工厂类,它将会被bind方法调用
Bootstrap localAddress(
SocketAddress)
指定Channel应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过bind或者connect方法指定localAddress
<T> Bootstrap option(
ChannelOption<T> option,
T value)
设置ChannelOption,其将被应用到每个新创建的Channel的ChannelConfig。这些选项将会通过bind或者connect方法设置到Channel,不管哪个先被调用。这个方法在Channel已经被创建后再调用将不会有任何的效果。支持的ChannelOption取决于使用的Channel类型。参见8.6节以及ChannelConfig的API文档,了解所使用的Channel类型
<T> Bootstrap attr(
Attribute<T> key, T value)
指定新创建的Channel的属性值。这些属性值是通过bind或者connect方法设置到Channel的,具体取决于谁最先被调用。这个方法在Channel被创建后将不会有任何的效果。参见8.6节
Bootstrap
handler(ChannelHandler)
设置将被添加到ChannelPipeline以接收事件通知的ChannelHandler
Bootstrap clone
创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap相同的设置信息
Bootstrap remoteAddress(
SocketAddress)
设置远程地址。或者,也可以通过connect方法来指定它
ChannelFuture connect
连接到远程节点并返回一个ChannelFuture,其将会在连接操作完成后接收到通知
ChannelFuture bind
绑定Channel并返回一个ChannelFuture,其将会在绑定操作完成后接收到通知,在那之后必须调用Channel. connect方法来建立连接
下一节将一步一步地讲解客户端的引导过程。我们也将讨论在选择可用的组件实现时保持兼容性的问题。
8.2.1 引导客户端
Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel,如图8-2所示。
图8-2 引导过程
代码清单8-1中的代码引导了一个使用NIO TCP传输的客户端。
代码清单8-1 引导一个客户端
EventLoopGroup group = new NioEventLoopGroup;
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap类的实例以创建和连接新的客户端Channel
bootstrap.group(group) ← -- 设置EventLoopGroup,提供用于处理Channel事件的EventLoop
.channel(NioSocketChannel.class) ← -- 指定要使用的Channel 实现
.handler(new SimpleChannelInboundHandler<ByteBuf> { ← -- 设置用于Channel 事件和数据的ChannelInboundHandler
@Override
protected void channeRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
} );
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80)); ← -- 连接到远程主机
future.addListener(new ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess) {
System.out.println("Connection established");
} else {
System.err.println("Connection attempt failed");
channelFuture.cause.printStackTrace;
}
}
} );
这个示例使用了前面提到的流式语法;这些方法(除了connect方法以外)将通过每次方法调用所返回的对Bootstrap实例的引用链接在一起。
8.2.2 Channel和EventLoopGroup的兼容性
代码清单8-2所示的目录清单来自io.netty.channel包。你可以从包名以及与其相对应的类名的前缀看到,对于NIO以及OIO传输两者来说,都有相关的EventLoopGroup和Channel实现。
代码清单8-2 相互兼容的EventLoopGroup和Channel
channel
├───nio
│ NioEventLoopGroup
├───oio
│ OioEventLoopGroup
└───socket
├───nio
│ NioDatagramChannel
│ NioServerSocketChannel
│ NioSocketChannel
└───oio
OioDatagramChannel
OioServerSocketChannel
OioSocketChannel
必须保持这种兼容性,不能混用具有不同前缀的组件,如NioEventLoopGroup和OioSocketChannel。代码清单8-3展示了试图这样做的一个例子。
代码清单8-3 不兼容的Channel和EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup;
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个新的Bootstrap类的实例,以创建新的客户端Channel
bootstrap.group(group) ← -- 指定一个适用于NIO 的EventLoopGroup 实现
.channel(OioSocketChannel.class) ← -- 指定一个适用于OIO 的Channel实现类
.handler(new SimpleChannelInboundHandler<ByteBuf> { ← -- 设置一个用于处理Channel的I/O 事件和数据的ChannelInboundHandler
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
} );
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80)); ← -- 尝试连接到远程节点
future.syncUninterruptibly;
这段代码将会导致IllegalStateException,因为它混用了不兼容的传输。
Exception in thread "main" java.lang.IllegalStateException:
incompatible event loop type: io.netty.channel.nio.NioEventLoop at
io.netty.channel.AbstractChannel$AbstractUnsafe.register(
AbstractChannel.java:571)
关于IllegalStateException的更多讨论
在引导的过程中,在调用bind或者connect方法之前,必须调用以下方法来设置所需的组件:
-
group;
-
channel或者channelFactory;
-
handler。
如果不这样做,则将会导致IllegalStateException。对handler方法的调用尤其重要,因为它需要配置好ChannelPipeline。
8.3 引导服务器
我们将从ServerBootstrap API的概要视图开始我们对服务器引导过程的概述。然后,我们将会探讨引导服务器过程中所涉及的几个步骤,以及几个相关的主题,包含从一个ServerChannel的子Channel中引导一个客户端这样的特殊情况。
8.3.1 ServerBootstrap类
表8-2列出了ServerBootstrap类的方法。
表8-2 ServerBootstrap类的方法
名 称
|
描 述
|
group
设置ServerBootstrap要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel和被接受的子Channel的I/O处理
channel
设置将要被实例化的ServerChannel类
channelFactory
如果不能通过默认的构造函数[8]创建Channel,那么可以提供一个Channel- Factory
localAddress
指定ServerChannel应该绑定到的本地地址。如果没有指定,则将由操作系统使用一个随机地址。或者,可以通过bind方法来指定该localAddress
option
指定要应用到新创建的ServerChannel的ChannelConfig的Channel- Option。这些选项将会通过bind方法设置到Channel。在bind方法被调用之后,设置或者改变ChannelOption都不会有任何的效果。所支持的ChannelOption取决于所使用的Channel类型。参见正在使用的ChannelConfig的API文档
childOption
指定当子Channel被接受时,应用到子Channel的ChannelConfig的ChannelOption。所支持的ChannelOption取决于所使用的Channel的类型。参见正在使用的ChannelConfig的API文档
attr
指定ServerChannel上的属性,属性将会通过bind方法设置给Channel。在调用bind方法之后改变它们将不会有任何的效果
childAttr
将属性设置给已经被接受的子Channel。接下来的调用将不会有任何的效果
handler
设置被添加到ServerChannel的ChannelPipeline中的ChannelHandler。更加常用的方法参见childHandler
childHandler
设置将被添加到已被接受的子Channel的ChannelPipeline中的Channel- Handler。handler方法和childHandler方法之间的区别是:前者所添加的ChannelHandler由接受子Channel的ServerChannel处理,而childHandler方法所添加的ChannelHandler将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字
clone
克隆一个设置和原始的ServerBootstrap相同的ServerBootstrap
bind
绑定ServerChannel并且返回一个ChannelFuture,其将会在绑定操作完成后收到通知(带着成功或者失败的结果)
下一节将介绍服务器引导的详细过程。
8.3.2 引导服务器
你可能已经注意到了,表8-2中列出了一些在表8-1中不存在的方法:childHandler、childAttr和childOption。这些调用支持特别用于服务器应用程序的操作。具体来说,ServerChannel的实现负责创建子Channel,这些子Channel代表了已被接受的连接。因此,负责引导ServerChannel的ServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子Channel的ChannelConfig的任务。
图8-3展示了ServerBootstrap在bind方法被调用时创建了一个ServerChannel,并且该ServerChannel管理了多个子Channel。
图8-3 ServerBootstrap和ServerChannel
代码清单8-4中的代码实现了图8-3中所展示的服务器的引导过程。
代码清单8-4 引导服务器
NioEventLoopGroup group = new NioEventLoopGroup;
ServerBootstrap bootstrap = new ServerBootstrap; ← -- 创建ServerBootstrap
bootstrap.group(group) ← -- 设置EventLoopGroup,其提供了用于处理Channel 事件的EventLoop
.channel(NioServerSocketChannel.class) ← -- 指定要使用的Channel 实现
.childHandler(new SimpleChannelInboundHandler<ByteBuf> { ← -- 设 置用于处理已被接受的子Channel的I/O及数据的ChannelInbound-Handler
@Override
protected void channelRead0(ChannelHandlerContext ctx,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
} );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); ← -- 通过配置好的ServerBootstrap的实例绑定该Channel
future.addListener(new ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess) {
System.out.println("Server bound");
} else {
System.err.println("Bound attempt failed");
channelFuture.cause.printStackTrace;
}
}
} );
8.4 从Channel引导客户端
假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web服务或者数据库)集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel中引导一个客户端Channel。
你可以按照8.2.1节中所描述的方式创建新的Bootstrap实例,但是这并不是最高效的解决方案,因为它将要求你为每个新创建的客户端Channel定义另一个EventLoop。这会产生额外的线程,以及在已被接受的子Channel和客户端Channel之间交换数据时不可避免的上下文切换。
一个更好的解决方案是:通过将已被接受的子Channel的EventLoop传递给Bootstrap的group方法来共享该EventLoop。因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。这个共享的解决方案如图8-4所示。
图8-4 在两个Channel之间共享EventLoop
实现EventLoop共享涉及通过调用group方法来设置EventLoop,如代码清单8-5所示。
代码清单8-5 引导服务器
ServerBootstrap bootstrap = new ServerBootstrap; ← -- 创建ServerBootstrap 以创建ServerSocketChannel,并绑定它
bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup) ← -- 设置EventLoopGroup,其将提供用以处理Channel 事件的EventLoop
.channel(NioServerSocketChannel.class) ← -- 指定要使用的Channel 实现
.childHandler( ← -- 设置用于处理已被接受的子Channel 的I/O 和数据的ChannelInboundHandler
new SimpleChannelInboundHandler<ByteBuf> {
ChannelFuture connectFuture;
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception {
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap类的实例以连接到远程主机
bootstrap.channel(NioSocketChannel.class).handler( ← -- 指定Channel的实现
new SimpleChannelInboundHandler<ByteBuf> { ← -- 为入站I/O 设置ChannelInboundHandler
@Override
protected void channelRead0(
ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
System.out.println("Received data");
}
} );
bootstrap.group(ctx.channel.eventLoop); ← -- 使用与分配给已被接受的子Channel 相同的EventLoop
connectFuture = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80)); ← -- 连接到远程节点
}
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
if (connectFuture.isDone) {
// do something with the data ← -- 当连接完成时,执行一些数据操作(如代理)
}
}
} );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); ← -- 通过配置好的ServerBootstrap绑定该Server-SocketChannel
future.addListener(new ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess) {
System.out.println("Server bound");
} else {
System.err.println("Bind attempt failed");
channelFuture.cause.printStackTrace;
}
}
} );
我们在这一节中所讨论的主题以及所提出的解决方案都反映了编写Netty应用程序的一个一般准则:尽可能地重用EventLoop,以减少线程创建所带来的开销。
8.5 在引导过程中添加多个ChannelHandler
在所有我们展示过的代码示例中,我们都在引导的过程中调用了handler或者child- Handler方法来添加单个的ChannelHandler。这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求。例如,一个必须要支持多种协议的应用程序将会有很多的ChannelHandler,而不会是一个庞大而又笨重的类。
正如你经常所看到的一样,你可以根据需要,通过在ChannelPipeline中将它们链接在一起来部署尽可能多的ChannelHandler。但是,如果在引导的过程中你只能设置一个ChannelHandler,那么你应该怎么做到这一点呢?
正是针对于这个用例,Netty提供了一个特殊的ChannelInboundHandlerAdapter子类:
public abstract class ChannelInitializer<C extends Channel>
extends ChannelInboundHandlerAdapter
它定义了下面的方法:
protected abstract void initChannel(C ch) throws Exception;
这个方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向Bootstrap或ServerBootstrap的实例提供你的Channel-Initializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel版本。在该方法返回之后,ChannelInitializer的实例将会从Channel-Pipeline中移除它自己。
代码清单8-6定义了ChannelInitializerImpl类,并通过ServerBootstrap的childHandler方法注册它[9]。你可以看到,这个看似复杂的操作实际上是相当简单直接的。
代码清单8-6 引导和使用ChannelInitializer
ServerBootstrap bootstrap = new ServerBootstrap; ← -- 创建ServerBootstrap 以创建和绑定新的Channel
bootstrap.group(new NioEventLoopGroup, new NioEventLoopGroup) ← -- 设置EventLoopGroup,其将提供用以处理Channel 事件的EventLoop
.channel(NioServerSocketChannel.class) ← -- 指定Channel 的实现
.childHandler(new ChannelInitializerImpl); ← -- 注册一个ChannelInitializerImpl 的实例来设置ChannelPipeline
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); ← -- 绑定到地址
future.sync;
final class ChannelInitializerImpl extends ChannelInitializer {[10] ← -- 用以设置ChannelPipeline 的自定义ChannelInitializerImpl 实现
@Override
protected void initChannel(Channel ch) throws Exception { ← -- 将所需的ChannelHandler添加到ChannelPipeline
ChannelPipeline pipeline = ch.pipeline;
pipeline.addLast(new HttpClientCodec);
pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
}
}
如果你的应用程序使用了多个ChannelHandler,请定义你自己的ChannelInitializer实现来将它们安装到ChannelPipeline中。
8.6 使用Netty的ChannelOption和属性
在每个Channel创建时都手动配置它可能会变得相当乏味。幸运的是,你不必这样做。相反,你可以使用option方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如keep-alive或者超时属性以及缓冲区设置。
Netty应用程序通常与组织的专有软件集成在一起,而像Channel这样的组件可能甚至会在正常的Netty生命周期之外被使用。在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey<T>(一个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户端和服务器Channel(包含ServerChannel的子Channel)相关联了。
例如,考虑一个用于跟踪用户和Channel之间的关系的服务器应用程序。这可以通过将用户的ID存储为Channel的一个属性来完成。类似的技术可以被用来基于用户的ID将消息路由给用户,或者关闭活动较少的Channel。
代码清单8-7展示了可以如何使用ChannelOption来配置Channel,以及如果使用属性来存储整型值。
代码清单8-7 使用属性值
final AttributeKey<Integer> id = AttributeKey.newInstance("ID"); [11] ← -- 创建一个AttributeKey以标识该属性
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap 类的实例以创建客户端Channel 并连接它们
bootstrap.group(new NioEventLoopGroup) ← -- 设置EventLoopGroup,其提供了用以处理Channel事件的EventLoop
.channel(NioSocketChannel.class) ← -- 指定Channel的实现
.handler(
new SimpleChannelInboundHandler<ByteBuf> { ← -- 设置用以处理Channel 的I/O 以及数据的Channel-InboundHandler
@Override
public void channelRegistered(ChannelHandlerContext ctx)
throws Exception {
Integer idValue = ctx.channel.attr(id).get; ← -- 使用AttributeKey 检索属性以及它的值
// do something with the idValue
}
@Override
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
System.out.println("Received data");
}
}
);
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); ← -- 设置ChannelOption,其将在connect或者bind方法被调用时被设置到已经创建的Channel 上
bootstrap.attr(id, 123456); ← -- 存储该id 属性
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.manning.com", 80)); ← -- 使用配置好的Bootstrap实例连接到远程主机
future.syncUninterruptibly;
8.7 引导DatagramChannel
前面的引导代码示例使用的都是基于TCP协议的SocketChannel,但是Bootstrap类也可以被用于无连接的协议。为此,Netty提供了各种DatagramChannel的实现。唯一区别就是,不再调用connect方法,而是只调用bind方法,如代码清单8-8所示。
代码清单8-8 使用Bootstrap和DatagramChannel
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap 的实例以创建和绑定新的数据报Channel
bootstrap.group(new OioEventLoopGroup).channel( ← -- 设置EventLoopGroup,其提供了用以处理Channel 事件的EventLoop
OioDatagramChannel.class).handler( ← -- 指定Channel的实现
new SimpleChannelInboundHandler<DatagramPacket>{ ← -- 设置用以处理Channel 的I/O 以及数据的Channel-InboundHandler
@Override
public void channelRead0(ChannelHandlerContext ctx,
DatagramPacket msg) throws Exception {
// Do something with the packet
}
}
);
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0)); ← -- 调用bind方法,因为该协议是无连接的
future.addListener(new ChannelFutureListener {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess) {
System.out.println("Channel bound");
} else {
System.err.println("Bind attempt failed");
channelFuture.cause.printStackTrace;
}
}
});
8.8 关闭
引导使你的应用程序启动并且运行起来,但是迟早你都需要优雅地将它关闭。当然,你也可以让JVM在退出时处理好一切,但是这不符合优雅的定义,优雅是指干净地释放资源。关闭Netty应用程序并没有太多的魔法,但是还是有些事情需要记在心上。
最重要的是,你需要关闭EventLoopGroup,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully方法的作用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。需要注意的是,shutdownGracefully方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future注册一个监听器以在关闭完成时获得通知。
代码清单8-9符合优雅关闭的定义。
代码清单8-9 优雅关闭
EventLoopGroup group = new NioEventLoopGroup; ← -- 创建处理I/O 的EventLoopGroup
Bootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap类的实例并配置它
bootstrap.group(group)
.channel(NioSocketChannel.class);
...
Future<?> future = group.shutdownGracefully; ← -- shutdownGracefully方法将释放所有的资源,并且关闭所有的当前正在使用中的Channel
// block until the group has shutdown
future.syncUninterruptibly;
或者,你也可以在调用EventLoopGroup.shutdownGracefully方法之前,显式地在所有活动的Channel上调用Channel.close方法。但是在任何情况下,都请记得关闭EventLoopGroup本身。
8.9 小结
在本章中,你学习了如何引导Netty服务器和客户端应用程序,包括那些使用无连接协议的应用程序。我们也涵盖了一些特殊情况,包括在服务器应用程序中引导客户端Channel,以及使用ChannelInitializer来处理引导过程中的多个ChannelHandler的安装。你看到了如何设置Channel的配置选项,以及如何使用属性来将信息附加到Channel。最后,你学习了如何优雅地关闭应用程序,以有序地释放所有的资源。
在下一章中,我们将研究Netty提供的帮助你测试你的ChannelHandler实现的工具。
[1] Channel继承了AttributeMap。——译者注
[2] 分层抽象。——译者注
[3] 应用程序的逻辑或实现。——译者注
[4] “拼图”指的是Netty的核心概念以及组件,也包括了如何完整正确地组织并且运行一个Netty应用程序。——译者注
[5] Java平台,标准版第8版API规范,java.lang,Interface Cloneable:http://docs.oracle.com/javase/8/docs/api/ java/lang/Cloneable.html。
[6] 被浅拷贝的EventLoopGroup。——译者注
[7] 这里指默认的无参构造函数,因为内部使用了反射来实现Channel的创建。——译者注
[8] 这里指无参数的构造函数。——译者注
[9] 注册到ServerChannel的子Channel的ChannelPipeline。——译者注
[10] 在大部分的场景下,如果你不需要使用只存在于SocketChannel上的方法,使用ChannelInitializer- 就可以了,否则你可以使用ChannelInitializer,其中SocketChannel扩展了Channel。——译者注
[11] 需要注意的是,AttributeKey上同时存在newInstance(String)和valueOf(String)方法,它们都可以用来获取具有指定名称的AttributeKey实例,不同的是,前者可能会在多线程环境下使用时抛出异常(实际上调用了createOrThrow(String)方法)——通常适用于初始化静态变量的时候;而后者(实际上调用了getOrCreate(String)方法)则更加通用(线程安全)。——译者注