Netty-Netty组件了解

EventLoop EventLoopGroup

        回想一下我们在 NIO 中是如何处理我们关心的事件的?在一个 while 循环中 select 出事
件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop interface
io.netty.channel. EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发
生的事件。
        io.netty.util.concurrent 包构建在 JDK java.util.concurrent 包上。 而 io.netty.channel 包
中的类,为了与 Channel 的事件进行交互,扩展了这些接口/类。 一个 EventLoop 将由一个
永远都不会改变的 Thread 驱动,同时任务( Runnable 或者 Callable )可以直接提交给
EventLoop 实现,以立即执行或者调度执行。

线程的分配

         服务于 Channel 的 I/O 和事件的 EventLoop 包含在 EventLoopGroup 中。
        异步传输实现只使用了少量的 EventLoop (以及和它们相关联的 Thread ),而且在当前
的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread
支撑大量的 Channel ,而不是每个 Channel 分配一个 Thread EventLoopGroup 负责为每个
新创建的 Channel 分配一个 EventLoop 在当前实现中,使用顺序循环(round-robin)的方
式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel
        一旦一个 Channel 被分配给一个 EventLoop ,它将在它的整个生命周期中都使用这个
EventLoop (以及相关联的 Thread )。

        需要注意,EventLoop 的分配方式对 ThreadLocal 的使用的影响。因为一个 EventLoop
常会被用于支撑多个 Channel ,所以对于所有相关联的 Channel 来说, ThreadLocal 都将是
一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上
下文中,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚
至是事件。

线程管理

         在内部,当提交任务到如果当前)调用线程正是支撑 EventLoop 的线程,那么所提交
的代码块将会被(直接)执行。 否则, EventLoop 将调度该任务以便稍后执行,并将它放入
到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务 / 事件。

ChannelEventLoop(Group)ChannelFuture

         Netty 网络抽象的代表:
        Channel—Socket;
        EventLoop—控制流、多线程处理、并发;
        ChannelFuture—异步通知。
        Channel 和 EventLoop 关系如图
        从图上我们可以看出 Channel 需要被注册到某个 EventLoop 上,在 Channel 整个生命周
期内都由这个EventLoop处理IO事件,也就是说一个Channel和一个EventLoop进行了绑定,
但是一个EventLoop可以同时被多个Channel绑定。 这一点在“ EventLoop EventLoopGroup
节里也提及过。

Channel 接口

        基本的 I/O 操作( bind() connect() read() write() )依赖于底层网络传输所提供的原
语。在基于 Java 的网络编程中,其基本的构造是类 Socket Netty Channel 接口所提供
API ,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。此外, Channel
也是拥有许多预定义的、专门化实现的广泛类层次结构的根。
        由于 Channel 是独一无二的,所以为了保证顺序将 Channel 声明为 java.lang.Comparable
的一个子接口。因此,如果两个不同的 Channel 实例都返回了相同的散列码,那么
AbstractChannel 中的 compareTo() 方法的实现将会抛出一个 Error
Channel 的生命周期状态
         ChannelUnregistered : Channel 已经被创建,但还未注册到 EventLoop
        ChannelRegistered : Channel 已经被注册到了 EventLoop
         ChannelActive : Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接
收和发送数据了
         ChannelInactive : Channel 没有连接到远程节点
         当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给 ChannelPipeline
中的 ChannelHandler,其可以随后对它们做出响应。在我们的编程中,关注 ChannelActive 和
ChannelInactive 会更多一些。
重要 Channel 的方法
         eventLoop: 返回分配给 Channel EventLoop
         pipeline: 返回 Channel ChannelPipeline ,也就是说每个 Channel 都有自己的
ChannelPipeline
         isActive: 如果 Channel 是活动的,则返回 true 。活动的意义可能依赖于底层的传输。
例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被
打开便是活动的。
         localAddress: 返回本地的 SokcetAddress
         remoteAddress: 返回远程的 SocketAddress
         write: 将数据写到远程节点,注意,这个写只是写往 Netty 内部的缓存,还没有真正
写往 socket
         flush: 将之前已写的数据冲刷到底层 socket 进行传输。
         writeAndFlush: 一个简便的方法,等同于调用 write() 并接着调用 flush()

ChannelPipeline ChannelHandlerContext

ChannelPipeline 接口

        当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline ,每个 Channel
有自己的 ChannelPipeline 。这项关联是永久性的。在 Netty 组件的生命周期中,这是一项固
定的操作,不需要开发人员的任何干预。
        ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播 入站(也
就是从网络到业务处理) 出站(也就是从业务处理到网络) ,各种事件流的 API ,我们
代码中的 ChannelHandler 都是放在 ChannelPipeline 中的。
        使得事件流经 ChannelPipeline ChannelHandler 的工作,它们是在应用程序的初始化
或者引导阶段被安装的。这些 ChannelHandler 对象接收事件、执行它们所实现的处理逻辑,
并将数据传递给链中的下一个 ChannelHandler ,而且 ChannelHandler 对象也完全可以拦截
事件不让事件继续传递。它们的执行顺序是由它们被添加的顺序所决定的。

ChannelHandler 的生命周期

        在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调
用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。
handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught 当处理过程中在 ChannelPipeline 中有错误产生时被调用

ChannelPipeline 中的 ChannelHandler

         入站和出站 ChannelHandler 被安装到同一个 ChannelPipeline 中,ChannelPipeline 以双
向链表的形式进行维护管理。比如下图,我们在网络上传递的数据,要求加密,但是加密后
密文比较大,需要压缩后再传输,而且按照业务要求,需要检查报文中携带的用户信息是否
合法,于是我们实现了 5 个 Handler: 解压(入)Handler、压缩(出)handler、解密(入)
Handler、加密(出) Handler、授权(入) Handler

        如果一个消息或者任何其他的入站事件被读取 那么它会从 ChannelPipeline 的头部开
始流动,但是只被处理入站事件的 Handler 处理,也就是解压(入)Handler、解密(入)Handler、 授权(入) Handler,最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。
        数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下, 数据将从
链的尾端开始流动,但是只被处理出站事件的 Handler 处理,也就是加密(出) Handler、
压缩(出)handler,直到它到达链的头部为止。 在这之后,出站数据将会到达网络传输层,
也就是我们的 Socket
        Netty 能区分入站事件的 Handler 和出站事件的 Handler ,并确保数据只会在具有相同定
向类型的两个 ChannelHandler 之间传递。

        所以在我们编写 Netty 应用程序时要注意,分属出站和入站不同的 Handler 在业务没
特殊要求的情况下 是无所谓顺序的,正如我们下面的图所示 比如‘压缩(出)handler‘可
以放在‘解压(入)handler‘和‘解密(入) Handler‘中间,也可以放在‘解密(入) Handler
‘和‘授权(入) Handler‘之间。
        而同属一个方向的 Handler 则是有顺序的,因为上一个 Handler 处理的结果往往是下一
Handler 的要求的输入。比如入站处理,对于收到的数据,只有先解压才能得到密文,才
能解密,只有解密后才能拿到明文中的用户信息进行授权检查,所以解压 -> 解密 -> 授权这个
三个入站 Handler 的顺序就不能乱。

ChannelPipeline 上的方法

        既然 ChannelPipeline 以双向链表的形式进行维护管理 Handler ,自然也提供了对应的方
法在 ChannelPipeline 中增加或者删除、替换 Handler
        addFirst、 addBefore addAfter addLast
        将一个 ChannelHandler 添加到 ChannelPipeline
        remove 将一个 ChannelHandler ChannelPipeline 中移除
        replace ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler
        get 通过类型或者名称返回 ChannelHandler
        context 返回和 ChannelHandler 绑定的 ChannelHandlerContext
        names 返回 ChannelPipeline 中所有 ChannelHandler 的名称
        ChannelPipeline 的 API 公开了用于调用入站和出站操作的附加方法。

ChannelHandlerContext

        ChannelHandlerContext 代表了 ChannelHandler ChannelPipeline 之间的关联,每当有
ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext ,为什么需
要这个 ChannelHandlerContext ?前面我们已经说过, ChannelPipeline 以双向链表的形式进
行维护管理 Handler ,毫无疑问, Handler 在放入 ChannelPipeline 的时候必须要有两个指针
pre next 来说明它的前一个元素和后一个元素,但是 Handler 本身来维护这两个指针合适
吗?想想我们在使用 JDK LinkedList 的时候,我们放入 LinkedList 的数据是不会带这两个指
针的, LinkedList 内部会用类 Node 对我们的数据进行包装,而类 Node 则带有两个指针 pre
next
        所以,ChannelHandlerContext 的主要作用就和 LinkedList 内部的类 Node 类似。
不过 ChannelHandlerContext 不仅仅只是个包装类,它还提供了很多的方法,比如让事
件从当前 ChannelHandler 传递给链中的下一个 ChannelHandler ,还可以被用于获取底层的
Channel ,还可以用于写出站数据。

ChannelChannelPipeline ChannelHandlerContext 上的事件传播

        ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel
Channel-Pipeline 本身上, 但是有一点重要的不同。 如果调用 Channel 或者 ChannelPipeline
的这些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用位于 ChannelHandlerContext
上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该
ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的 ChannelHandler

        我们用一个实际例子来说明,比如服务器收到对端发过来的报文,解压后需要进行解密,
结果解密失败,要给对端一个应答。
        如果发现解密失败原因是服务器和对端的加密算法不一致,应答报文只能以明文的压缩
格式发送,就可以在解密 handler 中直接使用 ctx.write 给对端应答,这样应答报文就只经过
压缩 Handler 就发往了对端;
        其他情况下,应答报文要以加密和压缩格式发送,就可以在解密 handler 中使用
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长情知热爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值