Netty 基本组件与线程模型

时间:2021-12-13 16:36:50

  Netty 的学习内容主要是围绕 TCP 和 Java NIO 这两个点展开的,由于 Netty 是基于 Java NIO 的 API 之上构建的网络通讯框架,Java NIO 中的几个组件,都能在 Netty 中找到对应的封装。下面我们就来一一熟悉 Netty 中的基本组件。

一、基本组件

Netty 的组件主要有以下 8 个:

  1. Channel
  2. ByteBuf
  3. ChannelHandler
  4. ChannelHandlerContext
  5. Pipeline
  6. EventLoop
  7. EventLoopGroup
  8. ServerBootstrap/Bootstrap

1.1 Channel

  Netty 中的 Channel 封装了 JDK 中原生的 Channel,所有对 Netty 中 Channel 的操作,最后都会转化成对原生 Channel 的操作。那么为什么要封装呢,主要有两点:1. 原生的 Channel 与 Netty 框架的结构不够兼容,所有 Netty 进行了一层包装,使其更符合 Netty 使用逻辑;2. 避免了对 SocketChannel 的直接操作,提供更直观和友好的 API 给开发人员。

  Netty 常用的是 NioSocketChannel 和 NioServerSocketChannel,对应了 JDK 中的 SocketChannel 和 ServerSocketChannel。Netty 中的 Channel 都有与之对应的 EventLoop 和 Pipeline。

1.2 ByteBuf

  ByteBuf 与 JDK 中的 ByteBuffer 类似。Netty 中的 ByteBuf 有基于 ByteBuffer 构建的,也有自身设计的其他实现。从不同的层级可以有多种划分方式,使用时主要关注的可能这 3 个方面:1. 池化与非池化;2. 堆内存与直接内存;3. 是否使用了 Unsafe 类来操作内存。关于这几点暂时有个大致的了解即可。

  ByteBuffer 只使用一个指针来保存读写的索引,使用起来比较麻烦,容易出错。而 ByteBuf 则使用了两个指针分别保存当前读和写的索引,使用起来就很方便,从 ByteBuf 读取数据时,只需要关注 readerIndex 即可。将数据写入 ByteBuf 时,只需要关注 writerIndex。ByteBuff 的容量 capacity 与两个指针之间的大小关系:0 <= readerIndex <= writerIndex <= capacity。

  Netty 源码中 ByteBuf 的类注释很好的展示了它的结构:

 /*
* +-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* | | (CONTENT) | |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
*/

1.3  ChannelHandler

  ChannelHandler 简言之就是一个处理器, 它的功能就是处理消息。它就像流水线上的工人,对每一个从他面前经过的部件进行加工。与工人稍有不同的地方是,它可以什么也不做,将消息直接交给下一个处理器,也可以直接将消息丢掉,不再传递。而且 ChannelHandler 是有方向的。对于开发人员来说,就是在 ChannelHandler 中编写业务逻辑代码,需要注意的是在 Handler 中不要执行耗时较大的业务逻辑,避免影响 IO。

1.4 ChannelHandlerContext

  从名称即可知道它是 ChannelHandler 的容器,每个 ChannelHandler 都有与之一一对应的 ChannelHandlerContext 对应。实际上,每个 ChannelHandler 并不直接交互,都是通过 ChannelHandlerContext 将彼此联系起来。ChannelHandlerContext 则是一个 Node,它有前驱和后继。对于一个 Channel 来说,它看到的是一个双向链表。

1.5 Pipeline

  Pipeline 中保存了由 ChannelHandlerContext 组成的双向链表。Netty 中的每个 Channel 都有一条自己的 Pipeline,每当该通道有需要处理的消息时,就会遍历 Pipeline 中的链表,通过每一个处理器来处理消息。Pipeline 的链表中默认就保存了一个 Head  和一个 Tail,所有用户添加的处理器都在这两个节点之间。下图就是包含一个用户处理器的 Pipeline:Netty 基本组件与线程模型

1.6 EventLoop

  EventLoop 可以简单的看作是一个线程,用来处理分配给它的 Channel 上的事件,也就是说一个 EventLoop 下面可能挂了多个 Channel。

1.7 EventLoopGroup

  从它的名称就可以知道它维护了一组 EventLoop,可以看作是一个 Netty 实现的线程池,负责给每一个新建里的 Channel 分配 EventLoop。

1.8 ServerBootstrap/Bootstrap

  这两个组件分别是用来启动服务端和客户端 ,在启动之前,可以通过这两个组件设定各种参数,添加 Handler,指定通道类型等。(Bootstrap 是鞋带的意思,为啥跟启动挂上勾了,可以参考知乎上的解答:Boot一词是为什么被用作计算机并作为引导解释的?或者说他的由来? - 知乎

二、Netty 线程模型

  Netty 采用的是 Reactor 线程模型,先从一个最简单的 HelloWorldServer 级别的线程模型来入手,如下图:Netty 基本组件与线程模型

  先了解一个 Channel 的建立过程:

  1. 服务端启动时,会启用一个线程并创建一个 NioServerSocketChannel 来监听指定的端口。这个线程上有一个 Selector,它关注的是 Accpet 事件;
  2. 当有客户端连接过来时,上图中的 EventLoop-0会创建一个 NioSocketChannel ,将该通道注册到 EventLoop-1 的 Selector 上,然后 EventLoop-1 就负责此后该 Channel 生命周期上所有的读写事件的处理;
  3. EventLoop-1 对其所属通道数据读写及其他处理,就通过 Pipeline 中的处理器链来实现。

  Netty 还包含了普通任务和定时任务的执行。

  参考资料:

  1. 『Netty 实战』- 中文版
  2. 『Netty 权威指南』- 第二版,这本书是基于 Netty 5 写的,虽然 Netty 5 项目已经关闭了,但是本书还是值得参考的
  3. Java读源码之Netty深入剖析-慕课网实战 - 基本上对源码的分析和了解,都是基于这个视频课程的内容,讲的挺好的