• 【探秘Netty】千字拆解netty


    首先我们思考一个问题:netty到底为我们做了什么?

    带着问题我们往下看


    1. netty内部到底有什么?

    • Bootstrap
      引导器,应用网络层配置。
    • Channel
      给底层网络传输API提供应用I/O操作的接口。如常用的读,写,连接,绑定等等。
    • ChannelHandler
      提供用于具体数据处理
    • ChannelPipeline
      提供了一个容器给 ChannelHandler 链并提供了一个API 用于管理沿着链入站和出站事件的流动。
    • EventLoop
      EventLoop 用于处理 Channel 的 I/O 操作
    • EventLoopGroup
      底层线程池,提供EventLoop容器
    • ChannelFuture
      提供了接口 ChannelFuture,它的 addListener 方法注册了一个 ChannelFutureListener ,当操作完成时,可以被通知(不管成功与否)。

    2. 为什么说Netty 是一个非阻塞、事件驱动的网络框架?

    因为你底层是使用EventLoopGroup创建线程池使用Threads(多线程)处理 I/O 事件,实际上再往底层钻是:EventLoop进行事件处理 ,以事件为中心(其实就是回调监听),并且使用Channel是一个底层为Nio的I/O处理器,Nio是一个同步的非阻塞模型,进行流程操作;
    在这里插入图片描述
    如图,每一channel对应一个eventloop进行事件处理,并且eventloop是存储在event loop Group中的

    3.为什么使用Bootstrap?

    Bootstrapping 有两种类型,一种是用于客户端的Bootstrap,一种是用于服务端的ServerBootstrap。不管程序使用哪种协议,无论是创建一个客户端还是服务器都需要使用“引导”。

    两种 Bootstrapping 之间有一些相似之处,也有一些不同。Bootstrap 和 ServerBootstrap 之间的差异如下:

    分类BootstrapServerBootstrap
    网络功能连接到远程主机和端口绑定本地端口
    EventLoopGroup12
    实现“ServerBootstrap”在服务器监听一个端口,轮询客户端的“Bootstrap”或DatagramChannel是否连接服务器。通常需要调用“Bootstrap”类的connect()方法,但是也可以先调用bind()再调用connect()进行连接,之后使用的Channel包含在bind()返回的ChannelFuture中。“Bootstrap”去主动链接一个服务端,连接完成后,使用的Channel包含在bind()返回的ChannelFuture中。

    4.ChannelHandler 和 ChannelPipeline有什么关系?

    概括性的部分在第一个问题中就说到了,这里就不在赘述,本问题主要讨论具体使用场景。

    其实ChannelPipeline就是一个单向链表,其中的每一个node就是ChannelHandler 。数据从出到入,都是按照你当时添加ChannelHandler 的顺序去处理的。

    ChannelHandler分为两种:ChannelOutboundHandler(出站处理器),ChannelInboundHandler(入站处理器)
    在这里插入图片描述
    每一个Handler中都有一个ChannelHandlerContext 代表着每一个处理器的上下文,这样你就可以继承:抽象基类 ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter,中的每一个监听方法(钩子函数)进行数据的处理。

    当 ChannelHandler 被添加到的 ChannelPipeline 后,它将会得到一个 ChannelHandlerContext,它代表一个 ChannelHandler 和 ChannelPipeline 之间的“绑定”。它通常是安全保存对此对象的引用,除了当协议中的使用的是不面向连接(例如,UDP)。而该对象可以被用来获得底层 Channel。

    还有,实际上,在 Netty 发送消息有两种方式:1.可以直接写消息给 Channel 、2.写入 ChannelHandlerContext 对象。主要的区别是, 前一种方法会导致消息从 ChannelPipeline的尾部开始,而 后者导致消息从 ChannelPipeline 下一个ChannelHandler 开始。因为你在ChannelPipeline 中才可以获取到ChannelHandlerContext 对象。

    5.探秘ChannelHandler

    核心就是编码器、解码器和一些钩子函数(被封装在适配器中,你可以继承ChannelInboundHandlerAdapter或者直接使用, ChannelInboundandlerAdapter的子类SimpleChannelInboundHandler)

    编码器与解码器

    当您发送或接收消息时,Netty 数据转换就发生了。入站消息将从字节转为一个Java对象;也就是说,“解码”。如果该消息是出站相反会发生:“编码”,从一个Java对象转为字节。其原因是简单的:网络数据是一系列字节,因此需要从那类型进行转换。

    对于入站数据,主要查看channelRead 方法被覆盖。该方法在每个消息从入站 Channel 读入时调用。该方法将调用特定解码器的“解码”方法,并将解码后的消息转发到管道中下个的 ChannelInboundHandler。

    出站消息是类似的。编码器将消息转为字节,转发到下个的 ChannelOutboundHandler。

    ByteBuf

    实现零拷贝,是我们编码、解码后的数据留存的地方,底层是一个动态数组,完美解决了半包以及粘包的问题。

    SimpleChannelHandler

    也许最常见的处理器是接收到解码后的消息并应用一些业务逻辑到这些数据。要创建这样一个 ChannelHandler,你只需要扩展基类SimpleChannelInboundHandler 其中 T 是想要进行处理的类型。这样的处理器,你将覆盖基类的一个或多个方法,将获得被作为输入参数传递所有方法的 ChannelHandlerContext 的引用。

    在这种类型的处理器方法中的最重要是 channelRead(ChannelHandlerContext chx,T t)。在这个调用中,T 是将要处理的消息。 你怎么做,完全取决于你,但无论如何你不能阻塞 I/O线程,因为这可能是不利于高性能。

    阻塞操作
    I/O 线程一定不能完全阻塞,因此禁止任何直接阻塞操作在你的 ChannelHandler, 有一种方法来实现这一要求。你可以指定一个 EventExecutorGroup 当添加 ChannelHandler 到ChannelPipeline。此 EventExecutorGroup 将用于获得EventExecutor,将执行所有的 ChannelHandler 的方法。这EventExecutor 将从 I/O 线程使用不同的线程,从而释放EventLoop。

    总结

    看完本文章,相信你对netty的基础组件就有了大概的认识,从Bootstrap封装最基础的网络连接(Socket)到EventLoopGroup(多线程,线程池模型)中的每一个EventLoop去把控全局,创建Channel和对应的处理器,再到ChannelHandle放置在ChannelPipeline形成处理链对出站入站数据进行处理,再到最小单元Channel的出现,封装了NIO让我们的I/O处理变成非阻塞的状态。每一步的环环相扣,形成了我们现在最流行的网络处理框架,目前大量流行的框架都是基于Netty的,比如:Dubbo,Rocketmq,所以学好他是有必要的,希望大家能够有所收获。

    在这里插入图片描述

  • 相关阅读:
    SpringMVC Day 04 : 数据绑定
    (pytorch进阶之路)cGAN、LSGAN
    网络传输中的重要参数-简单的网络画像
    阿里云SSL免费证书到期自动申请部署程序
    【操作系统笔记】南京大学jyy老师
    使用域名转发mqtt协议,避坑指南
    开发者福利!李彦宏将在百度世界大会手把手教你做AI原生应用
    Elasticsearch基本操作-RESTful操作(更新中)
    【代码精读】optee的进入和退出的方式
    Java学习----UDP和反射
  • 原文地址:https://blog.csdn.net/weixin_42842069/article/details/126350514