博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Netty——Bootstrap类
阅读量:2444 次
发布时间:2019-05-10

本文共 13302 字,大约阅读时间需要 44 分钟。

Bootstrap类

引导类的层次结构包括一个抽象的父类和两个具体的引导子类:

在这里插入图片描述
相对于将具体的引导类分别看作用于服务器和客户端的引导来说,记住它们的本意是用来支撑不同的应用程序的功能的将有所裨益。也就是说,服务器致力于使用一个父 Channel 来接受来自客户端的连接,并创建子 Channel 以用于它们之间的通信;而客户端将最可能只需要一个 单独的、没有父 Channel 的 Channel 来用于所有的网络交互。(正如同我们将要看到的,这也适用于无连接的传输协议,如 UDP,因为它们并不是每个连接都需要一个单独的 Channel。)

为什么引导类是 Cloneable 的?

你有时可能会需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了 Cloneable。在一个已经配置完成的引导类实例上调用clone()方法将返回另一个可以立即使用的引 导类实例。

注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以,后者将在所有克隆的Channel实例之间共享。这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂,一 个典型的场景是——创建一个Channel以进行一次HTTP请求。

AbstractBootstrap 类的完整声明是: public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C>,C extends Channel>

在这个签名中,子类型 B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以
支持方法的链式调用(也就是所谓的流式语法)。

其子类的声明如下:

public class Bootstrap extends AbstracBootstrap
public class ServerBootstrap extends AbstractBootstrap

引导客户端和无连接协议

Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。

在这里插入图片描述
在这里插入图片描述

引导客户端

Bootstrap 类负责为客户端和使用无连接协议的应用程序创建 Channel:

在这里插入图片描述

public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup(); //创建一个Bootstrap类的实例以创建和连接新的客户端Channel Bootstrap bootstrap = new Bootstrap(); //设置EventLoopGroup提供用于处理Channel事件的EventLoop bootstrap.group(group) .channel(NioSocketChannel.class)//指定要使用的Channel实现 .handler(new SimpleChannelInboundHandler
() {
//设置用于Channel事件和数据的ChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data"); } }); //连接到远程主机 ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8080)); future.addListener(new ChannelFutureListener() {
@Override public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Connection established"); } else {
System.out.println("Connection attempt failed"); future.cause().printStackTrace(); } } }); }

Channel和EventLoopGroup的兼容性

对于 NIO 以及 OIO 传输两者来说,都有相关的 EventLoopGroup 和 Channel 实现:

在这里插入图片描述
必须保持这种兼容性,不能混用具有不同前缀的组件,如 NioEventLoopGroup 和 OioSocketChannel。

注意:在引导的过程中,在调用 bind()或者 connect()方法之前,必须调用以下方法来设置所需的组件:

  • group();
  • channel()或者 channelFactory();
  • handler()。

如果不这样做,则将会导致 IllegalStateException。对 handler()方法的调用尤其重要,因为它需要配置好 ChannelPipeline。

引导服务器

ServerBootstrap类的方法:

在这里插入图片描述
ServerChannel 的实现负责创建子 Channel,这些子 Channel 代表了已被接受的连接。因此,负责引导 ServerChannel 的 ServerBootstrap 提供了这些方法,以简化将设置应用到 已被接受的子 Channel 的 ChannelConfig 的任务。

下图展示了 ServerBootstrap 在 bind()方法被调用时创建了一个 ServerChannel, 并且该 ServerChannel 管理了多个子 Channel:

在这里插入图片描述

public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup(); //创建ServerBootstrap ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其提供了用于处理Channel事件的EventLoop bootstrap.group(group) .channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler
() {
@Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Reveived data"); } }); //通过配置好的ServerBootstrap的实例绑定该Channel ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() {
@Override public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Server bound"); } else {
System.out.println("Bound attempt failed"); future.cause().printStackTrace(); } } }); }

从Channel引导客户端

假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当 一个应用程序(如一个代理服务器)必须要和组织现有的系统(如 Web 服务或者数据库)集成 时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子 Channel 中引导一个客户 端 Channel。

可以按照上面所描述的方式创建新的 Bootstrap 实例,但是这并不是最高效的解决方案,因为它将要求你为每个新创建的客户端 Channel 定义另一个 EventLoop。这会产生 额外的线程,以及在已被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上 下文切换。

一个更好的解决方案是:通过将已被接受的子 Channel 的 EventLoop 传递给 Bootstrap 的 group()方法来共享该 EventLoop。因为分配给 EventLoop 的所有 Channel 都使用同一 个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换,如下图所示:

在这里插入图片描述

实现 EventLoop 共享涉及通过调用 group()方法来设置 EventLoop:

public static void main(String[] args) {
//创建ServerBootstrap以创建ServerSocketChannel,并绑定它 ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(//设置用于处理已被接收的子Channel的I/O和数据的ChannelInboundHandler new SimpleChannelInboundHandler
() {
ChannelFuture connectFuture; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception {
//创建一个Bootstrap类的实例以连接到远程主机 Bootstrap bootstrap = new Bootstrap(); //指定Channel的实现,为入站I/O设置ChannelInboundHandler bootstrap.channel(NioSocketChannel.class).handler( new SimpleChannelInboundHandler
() {
@Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data"); } }); //使用与分配给已被接受的子Channel相同的EventLoop bootstrap.group(ctx.channel().eventLoop()); //连接到远程端点 connectFuture = bootstrap.connect( new InetSocketAddress("localhost", 80)); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
if (connectFuture.isDone()) {
//当连接完成时,执行一些数据操作(如代理) } } }); //通过配置好的ServerBootstrap绑定该ServerSocketChannel ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() {
@Override public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Server bound"); } else {
System.out.println("Bind attempt failed"); future.cause().printStackTrace(); } } }); }

在引导过程中添加多个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 的实例提供你的 ChannelInitializer 实现即可,并且一旦 Channel 被注册到了它的 EventLoop 之后,就会调用你的 initChannel()版本。在该方法返回之后,ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。

下面代码定义了ChannelInitializerImpl 类 , 并 通 过 ServerBootstrap 的 childHandler()方法注册它。你可以看到,这个看似复杂的操作实际上是相当简单直接的:

public class ChannelInitializerDemo {
public static void main(String[] args) throws InterruptedException {
//创建ServerBootstrap以创建和绑定新的Channel ServerBootstrap bootstrap = new ServerBootstrap(); //设置EventLoopGroup,其将提供用以处理Channel事件的EventLoop bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerImpl()); ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.sync(); }}final class ChannelInitializerImpl extends ChannelInitializer
{
@Override protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); }}

如果你的应用程序使用了多个 ChannelHandler,请定义你自己的 ChannelInitializer 实现来将它们安装到 ChannelPipeline 中。

使用Netty的ChannelOption和属性

在每个 Channel 创建时都手动配置它可能会变得相当乏味。幸运的是,你不必这样做。相 反,你可以使用 option()方法来将 ChannelOption 应用到引导。你所提供的值将会被自动 应用到引导所创建的所有 Channel。可用的 ChannelOption 包括了底层连接的详细信息,如 keep-alive 或者超时属性以及缓冲区设置。

Netty 应用程序通常与组织的专有软件集成在一起,而像 Channel 这样的组件可能甚至会在 正常的 Netty 生命周期之外被使用。在某些常用的属性和数据不可用时,Netty 提供了 AttributeMap 抽象(一个由 Channel 和引导类提供的集合)以及 AttributeKey(一 个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户 端和服务器 Channel(包含 ServerChannel 的子 Channel)相关联了。

例如,考虑一个用于跟踪用户和 Channel 之间的关系的服务器应用程序。这可以通过将用 户的 ID 存储为 Channel 的一个属性来完成。类似的技术可以被用来基于用户的 ID 将消息路由 给用户,或者关闭活动较少的 Channel。

下面的代码展示了可以如何使用 ChannelOption 来配置 Channel,以及如果使用属性 来存储整型值:

public static void main(String[] args) {
//创建一个AttributeKey以标识该属性 final AttributeKey
id = new AttributeKey
("ID"); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new NioEventLoopGroup()) .channel(NioSocketChannel.class) .handler(new SimpleChannelInboundHandler
() {
@Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//使用 AttributeKey 检索 属性以及它的值 Integer idValue = ctx.channel().attr(id).get(); //do something with the idValue } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data"); } }); // 设置 ChannelOption, 其将在 connect()或者 bind()方法被调用时 被设置到已经创建的 Channel 上 bootstrap.option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); //存储该id属性 bootstrap.attr(id, 123456); ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 80)); future.syncUninterruptibly(); }

引导DatagramChannel

前面的引导代码示例使用的都是基于 TCP 协议的 SocketChannel,但是 Bootstrap 类 也可以被用于无连接的协议。为此,Netty 提供了各种 DatagramChannel 的实现。唯一区别就 是,不再调用 connect()方法,而是只调用 bind()方法:

public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new OioEventLoopGroup()) .channel(OioDatagramChannel.class) .handler( new SimpleChannelInboundHandler
() {
@Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
//Do somethind with the packet } }); //调用bind方法,因为该协议是无连接的 ChannelFuture future = bootstrap.bind(new InetSocketAddress(0)); future.addListener(new ChannelFutureListener() {
@Override public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) System.out.println("Channel bound"); else {
System.out.println("Bind attempt failed"); future.cause().printStackTrace(); } } }); }

关闭

引导使你的应用程序启动并且运行起来,但是迟早你都需要优雅地将它关闭。当然,你也可 以让 JVM 在退出时处理好一切,但是这不符合优雅的定义,优雅是指干净地释放资源。关闭 Netty 应用程序并没有太多的魔法,但是还是有些事情需要记在心上。

最重要的是,你需要关闭 EventLoopGroup,它将处理任何挂起的事件和任务,并且随后 释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully()方法的作用。 这个方法调用将会返回一个 Future,这个 Future 将在关闭完成时接收到通知。需要注意的是, shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向 所返回的 Future 注册一个监听器以在关闭完成时获得通知。

//优雅的关闭EventLoopGroup group = new NioEventLoopGroup();        Bootstrap bootstrap = new Bootstrap();        bootstrap.group(group)                .channel(NioSocketChannel.class);        ...        // shutdownGracefully()方法将释放 所有的资源,并且关闭所有的当 前正在使用中的 Channel        Future
future = group.shutdownGracefully(); //block until the group hai shutdown future.syncUninterruptibly();

或者,你也可以在调用 EventLoopGroup.shutdownGracefully()方法之前,显式地 在所有活动的 Channel 上调用 Channel.close()方法。但是在任何情况下,都请记得关闭 EventLoopGroup 本身。

转载地址:http://mlpqb.baihongyu.com/

你可能感兴趣的文章
rxjs 搜索_如何使用RxJS构建搜索栏
查看>>
如何在Debian 10上安装MariaDB
查看>>
go函数的可变长参数_如何在Go中使用可变参数函数
查看>>
react-notifications-component,一个强大的React Notifications库
查看>>
如何在Debian 10上设置SSH密钥
查看>>
angular4前后端分离_如何在Angular 4+中使用Apollo客户端GraphQL
查看>>
如何在Ubuntu 18.04上安装Apache Kafka
查看>>
如何在Ubuntu 20.04上安装R [快速入门]
查看>>
debian tomcat_如何在Debian 10上安装Apache Tomcat 9
查看>>
如何为Python 3设置Jupyter Notebook
查看>>
express中间件_创建自己的Express.js中间件
查看>>
如何在Ubuntu 18.04上使用Docker和Caddy远程访问GUI应用程序
查看>>
Apache配置错误AH00558:无法可靠地确定服务器的标准域名
查看>>
apache 证书配置_Apache配置错误AH02572:无法配置至少一个证书和密钥
查看>>
web设置字体粗细css_Web上使用CSS的可变字体
查看>>
css 垂直对齐_CSS垂直对齐属性
查看>>
为您的网站提供动力的100种Jamstack工具,API和服务
查看>>
api restful_构建RESTful API的13种最佳实践
查看>>
wordpress用途_8个热门WordPress多用途主题及其炫酷功能
查看>>
用于Angular,React和Vue.js的Bootstrap UI库
查看>>