本章主要内容
- 引导客户端和服务器
- 从
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类的实例,以创建新的客户端Channelbootstrap.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 atio.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; ← -- 创建ServerBootstrapbootstrap.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的实例绑定该Channelfuture.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-SocketChannelfuture.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 的实例以创建和绑定新的数据报Channelbootstrap.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 的EventLoopGroupBootstrap bootstrap = new Bootstrap; ← -- 创建一个Bootstrap类的实例并配置它bootstrap.group(group) .channel(NioSocketChannel.class);...Future<?> future = group.shutdownGracefully; ← -- shutdownGracefully方法将释放所有的资源,并且关闭所有的当前正在使用中的Channel// block until the group has shutdownfuture.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)
方法)则更加通用(线程安全)。——译者注