本文参考

本篇文章是对《Netty In Action》一书第八章"引导"的学习摘记,主要内容为引导客户端和服务端、从channel内引导客户端、添加ChannelHandler和使用ChanneOption

引导类层次结构

服务端ServerBootstrap和客户端Bootstrap都继承和实现了抽象类AbstractBootstrap

抽象类AbstractBootstrap实现了Cloneable接口,当需要创建多个具有类似配置或者完全相同配置的Channel时,不需要为每个Channel都创建并配置一个新的引导类实例,在一个已经配置完成的引导类实例上调用clone()方法将返回另一个可以立即使用的引导类实例

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

public abstract B clone();

Returns a deep clone of this bootstrap which has the identical configuration. This method is useful when making multiple Channels with similar settings. Please note that this method does not clone the EventLoopGroup deeply but shallowly, making the group a shared resource

Bootstrap类引导客户端

A Bootstrap that makes it easy to bootstrap a Channel to use for clients.

The bind() methods are useful in combination with connectionless transports such as datagram (UDP). For regular TCP connections, please use the provided connect() methods.

Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel,原书中的此图我认为有错误,调用bind()方法创建的是UDP连接,并不会再调用connect()方法

下面的代码引导了一个使用NIO TCP传输的客户端

//设置 EventLoopGroup,提供用于处理 Channel 事件的 EventLoop
EventLoopGroup
group = new NioEventLoopGroup();
//创建一个Bootstrap类的实例以创建和连接新的客户端Channel
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)

  //指定要使用的Channel 实现

  .channel(NioSocketChannel.class)

  //设置用于 Channel 事件和数据的ChannelInboundHandler

  .handler(new SimpleChannelInboundHandler<ByteBuf>() {

    @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.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();
    }
  }
});

注意EventLoopGroup和Channel传输类型的兼容,OIO和NIO不可混用,以及在调用bind()或者connect()方法之前,必须调用group()、channel()或者channelFactory()、handler()方法添加组件,否则将会导致IllegalStateException异常

ServerBootstrap类引导服务器

Bootstrap sub-class which allows easy bootstrap of ServerChannel

在前面"深入了解Netty核心组件"的文章中,我们已经了解到Server端需要配置两个EventLoopGroup,第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字,而第二组将包含所有已创建的用来处理传入客户端连接的 Channel

此处我们可以看到服务端和客户端的不同之处

服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父Channel的Channel来用于所有的网络交互

因此在ServerBootstrap类中存在Bootstrap类所没有的方法:childHandler()、 childAttr()和childOption()

拿handler()和childHandler()两个方法来说,handler()设置被添加到ServerChannel的ChannelPipeline中的ChannelHandler,而childHandler()设置将被添加到已被接受的子Channel的ChannelPipeline中的Channel- Handler。前者所添加的 ChannelHandler 由接受子 Channel 的 ServerChannel 处理,而 childHandler()方法所添加的ChannelHandler将由已被接受的子Channel 处理,其代表一个绑定到远程节点的套接字

下面的代码引导了一个使用NIO TCP传输的服务端

NioEventLoopGroup group = new NioEventLoopGroup();
//创建 Server Bootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
//设置 EventLoopGroup,其提供了用于处理 Channel 事件的EventLoop
bootstrap.group(group)

  //指定要使用的 Channel 实现

  .channel(NioServerSocketChannel.class)

  //设置用于处理已被接受的子 Channel 的I/O及数据的 ChannelInboundHandler

  .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {

    @Override

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
    throws Exception {

      System.out.println("Received data");
    }
});
//通过配置好的 ServerBootstrap 的实例绑定该 Channel
ChannelFuture
future = bootstrap.bind(new InetSocketAddress(8080));
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();
    }
  }
});

从Channel引导客户端

若有一个第三方的客户端接入现有的服务端 / 客户端连接,我们可以通过将已被接受的子Channel的EventLoop传递给Bootstrap 的group()方法来共享该EventLoop,以此避免创建一个新的Bootstrap和额外的线程开销,这种共享的解决方案如图所示

//创建 ServerBootstrap 以创建 ServerSocketChannel,并绑定它
ServerBootstrap bootstrap = new ServerBootstrap();
//设置 EventLoopGroup,其将提供用以处理 Channel 事件的 EventLoop
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())

  //指定要使用的 Channel 实现

  .channel(NioServerSocketChannel.class)

  //设置用于处理已被接受的子 Channel 的 I/O 和数据的 ChannelInboundHandler

  .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {

    ChannelFuture connectFuture;

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

      //创建一个 Bootstrap 类的实例以连接到远程主机

      Bootstrap bootstrap = new Bootstrap();

      //指定 Channel 的实现

      bootstrap.channel(NioSocketChannel.class).handler(

        //为入站 I/O 设置 ChannelInboundHandler

        new
SimpleChannelInboundHandler<ByteBuf>() {

          @Override

          protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {

            System.out.println("Received data");
          }
      });

      //使用与分配给已被接受的子Channel相同的EventLoop

      bootstrap.group(ctx.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

        }
      }
});
//通过配置好的 ServerBootstrap 绑定该 ServerSocketChannel
ChannelFuture
future = bootstrap.bind(new InetSocketAddress(8080));
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();
    }
  }
});

在引导过程中添加多个ChannelHandler

我们在"Netty客户端 / 服务端概览"一文中已经接触了这中添加ChannelHandler的方式,Netty 提供了一个特殊的ChannelInboundHandlerAdapter子类ChannelInitializer

A special ChannelInboundHandler which offers an easy way to initialize a Channel once it was registered to its EventLoop. Implementations are most often used in the context of Bootstrap.handler(ChannelHandler) , ServerBootstrap.handler(ChannelHandler) and ServerBootstrap.childHandler(ChannelHandler) to setup the ChannelPipeline of a Channel.

它的initChannel方法法提供了一种将多个 ChannelHandler 添加到一个 ChannelPipeline 中的简便方法。我们只需要简单地向 Bootstrap 或 ServerBootstrap 的实例提供你的 ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用 initChannel(),在该方法返回之后,ChannelInitializer的实例将会从 ChannelPipeline中移除它自己

下面的代码定义了ChannelInitializerImpl类,并通过ServerBootstrap的 childHandler()方法注册它

public void bootstrap() throws InterruptedException {

  //创建 ServerBootstrap 以创建和绑定新的 Channel

  ServerBootstrap bootstrap = new ServerBootstrap();

  //设置 EventLoopGroup,其将提供用以处理 Channel 事件的 EventLoop

  bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())

    //指定 Channel 的实现

    .channel(NioServerSocketChannel.class)

    //注册一个 ChannelInitializerImpl 的实例来设置 ChannelPipeline

    .childHandler(new ChannelInitializerImpl());

    //绑定到地址

  ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));

  future.sync();
}

//用以设置 ChannelPipeline 的自定义 ChannelInitializerImpl 实现
//在大部分的场景下,如果不需要使用只存在于SocketChannel上的方法使用ChannelInitializer<Channel>即可
//否则你可以使用ChannelInitializer<SocketChannel>,其中 SocketChannel 扩展了Channel
final class ChannelInitializerImpl extends ChannelInitializer<Channel> {

  @Override

  //将所需的 ChannelHandler 添加到 ChannelPipeline

  protected void
initChannel(Channel ch) throws Exception {

    ChannelPipeline pipeline = ch.pipeline();

    pipeline.addLast(new HttpClientCodec());

    pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
  }
}

ChannelOption和attr属性

A ChannelOption allows to configure a ChannelConfig in a type-safe way. Which ChannelOption is supported depends on the actual implementation of ChannelConfig and may depend on the nature of the transport it belongs to.

option()-> 指定要应用到新创建的 ServerChannel 的 ChannelConfig 的 ChannelOption。这些选项将会通过bind()方法设置到Channel。在 bind()方法 被调用之后,设置或者改变 ChannelOption 都不会有任何的效果,另外还有针对子Channel的childOption()方法

attr() -> 指定ServerChannel上的属性,属性将会通过bind()方法设置给Channel。 在调用bind()方法之后改变它们将不会有任何的效果,另外还有针对子Channel的childAttr()方法

我们可以使用 option()方法来将 ChannelOption 应用到引导。我们提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如 keep-alive或者超时属性以及缓冲区设置

Netty 提供了 AttributeMap 抽象(一个由 Channel 和引导类提供的集合)以及 AttributeKey<T>(一 个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户 端和服务器Channel(包含ServerChannel的子Channel)相关联

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

//创建一个 AttributeKey 以标识该属性
final AttributeKey<Integer> id = AttributeKey.newInstance("ID");
//创建一个 Bootstrap 类的实例以创建客户端 Channel 并连接它们
Bootstrap bootstrap = new Bootstrap();
//设置 EventLoopGroup,其提供了用以处理 Channel 事件的 EventLoop
bootstrap.group(new NioEventLoopGroup())

  //指定 Channel 的实现

  .channel(NioSocketChannel.class)
  .handler(

  //设置用以处理 Channel 的 I/O 以及数据的 ChannelInboundHandler

    new
SimpleChannelInboundHandler<ByteBuf>() {

      @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 channelHandlerContext,ByteBuf byteBuf)
    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);
//使用配置好的 Bootstrap 实例连接到远程主机
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.manning.com", 80));
future.syncUninterruptibly();

引导DatagramChannel

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

//创建一个 Bootstrap 的实例以创建和绑定新的数据报 Channel
Bootstrap bootstrap = new Bootstrap();
//设置 EventLoopGroup,其提供了用以处理 Channel 事件的 EventLoop
bootstrap.group(new OioEventLoopGroup()).channel(

//指定 Channel 的实现

OioDatagramChannel.class).handler(

//设置用以处理 Channel 的 I/O 以及数据的 ChannelInboundHandler

  new
SimpleChannelInboundHandler<DatagramPacket>() {

    @Override

    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {

      // Do something with the packet

    }
  }
);
//调用 bind() 方法,因为该协议是无连接的
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));
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();
    }
  }
});

关闭

最重要的是,我们需要关闭EventLoopGroup,即EventLoopGroup.shutdownGracefully()方法,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知

注意,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future注册一个监听器以在关闭完成时获得通知

//创建处理 I/O 的EventLoopGroup
EventLoopGroup
group = new NioEventLoopGroup();
//创建一个 Bootstrap 类的实例并配置它
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
  .channel(NioSocketChannel.class)
  //...

  .handler(new SimpleChannelInboundHandler<ByteBuf>() {

    @Override

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf)
    throws Exception {

      System.out.println("Received data");
    }
  }
);
bootstrap.connect(new InetSocketAddress("www.manning.com", 80)).syncUninterruptibly();
//...
//shutdownGracefully()
方法将释放所有的资源,并且关闭所有的当前正在使用中的 Channel
Future
<?> future = group.shutdownGracefully();
// block until the group has shutdown
future.syncUninterruptibly();

在最后我们调用了syncUninterruptibly()方法,它和sync()方法的区别如下:

/**
 * Waits for this future until it is done, and rethrows the cause of the failure if this future
 * failed.
 */
Future<V> sync() throws InterruptedException;

/**
 * Waits for this future until it is done, and rethrows the cause of the failure if this future
 * failed.
 */
Future<V> syncUninterruptibly();

Netty学习摘记 —— 再谈引导的更多相关文章

  1. Netty学习摘记 —— 再谈EventLoop 和线程模型

    本文参考 本篇文章是对<Netty In Action>一书第七章"EventLoop和线程模型"的学习摘记,主要内容为线程模型的概述.事件循环的概念和实现.任务调度和 ...

  2. Netty学习摘记 —— 再谈ChannelHandler和ChannelPipeline

    本文参考 本篇文章是对<Netty In Action>一书第六章"ChannelHandler和ChannelPipeline",主要内容为ChannelHandle ...

  3. Netty学习摘记 —— 初步认识Netty核心组件

    本文参考 我在博客内关于"Netty学习摘记"的系列文章主要是对<Netty in action>一书的学习摘记,文章中的代码也大多来自此书的github仓库,加上了一 ...

  4. Netty学习摘记 —— Netty客户端 / 服务端概览

    本文参考 本篇文章是对<Netty In Action>一书第二章"你的第一款 Netty 应用程序"的学习摘记,主要内容为编写 Echo 服务器和客户端 第一款应用程 ...

  5. Netty学习摘记 —— UDP广播事件

    本文参考 本篇文章是对<Netty In Action>一书第十三章"使用UDP广播事件"的学习摘记,主要内容为广播应用程序的开发 消息POJO 我们将日志信息封装成名 ...

  6. Netty学习摘记 —— 简单WEB聊天室开发

    本文参考 本篇文章是对<Netty In Action>一书第十二章"WebSocket"的学习摘记,主要内容为开发一个基于广播的WEB聊天室 聊天室工作过程 请求的 ...

  7. Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议

    本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...

  8. Netty学习摘记 —— 初识编解码器

    本文参考 本篇文章是对<Netty In Action>一书第十章"编解码器框架"的学习摘记,主要内容为解码器和编码器 编解码器实际上是一种特殊的ChannelHand ...

  9. Netty学习摘记 —— ByteBuf详解

    本文参考 本篇文章是对<Netty In Action>一书第五章"ByteBuf"的学习摘记,主要内容为JDK 的ByteBuffer替代品ByteBuf的优越性 你 ...

随机推荐

  1. Win10系统下关闭管理员运行确认弹窗

    Windows10及以上系统对于安全的考虑,对于程序运行时的权限做了控制.    点击后,会弹出确认的弹窗. 像我做测试,或者使用cmd经常需要administrator 权限,一直弹弹弹就很烦. 要 ...

  2. 【C# .Net GC】开篇

    前言 自从.NET Core 3.0开始对根据自己具体的应用场景去配置GC ,让GC 发挥最好的作用..NET 5 改动更大,而且.NET 5整体性能比.net core 3.1高20%,并且在GC这 ...

  3. WPF中使用OpenFileDialog打开文件

    添加Microsoft.Win32程序集 private void OnOpenFile(object sender, EventArgs e) { OpenFileDialog openFileDi ...

  4. HMS Core挑战赛故事:鞋、街景、手办、玩具,原来这些都可以3D建模

    HMS Core线上Codelabs挑战赛第3期中,开发者通过学习和运用HMS Core开发的3D建模服务,生成3D建模应用demo,再使用demo为自己身边的一个实物完成建模.在提交的作品中,小编发 ...

  5. java jdbc连接池

    public class C3P0Util { //1. 在成员变量位置创建一个静态的ComboPooledDtatSource 对象 private static ComboPooledDataSo ...

  6. Python:爬取一个可下载的PDF链接并保存为本地pdf文件

    问题:网页http://gk.chengdu.gov.cn/govInfo/detail.action?id=2653973&tn=2中有一个PDF需要下载,开发者模式下该PDF的链接为htt ...

  7. C# Tab键TabIndex使用问题(顺序,不起作用,跳过某个元素等问题)

    C#.net语言,winform程序.一个画面中有多个控件,但是在添加的时候顺序是错的,所以现在想改Tab顺序,需要用到TabIndex ,如何设置控件TabIndex 1.选中窗口控件-右键-属性, ...

  8. python3中zip()的用法

    zip函数接受任意多个可迭代对象作为参数,将对象中对应的元素打包成一个tuple,然后返回一个可迭代的zip对象. 这个可迭代对象可以使用循环的方式列出其元素 若多个可迭代对象的长度不一致,则所返回的 ...

  9. vue全局引入公共scss样式,子组件调用

    前提 已引用并使用scss npm install sass-loader --save-dev npm install node-sass --sava-dev 配置 在vue.config.js中 ...

  10. idea Mybatis mapper配置文件删除SQL语句背景色

    原样式: 看着很不爽 本文 idea 版本为:idea 2020.3.1,以下操作均基于此版本进行 解决办法 1.去除警告 Settings>Editor>Inspections>S ...