Netty核心组件与源码分析

核心组件

1.EventLoop和EventLoopGroup

  EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel。一旦一个 Channel 被分配给一个 EventLoop,它的整个生命周期中都使用这一个EventLoop(以及相关联的 Thread)。
  需要注意的是,在提交任务时,如果当前调用线程正好是支撑 EventLoop 的线程,那么所提交的代码块将会被直接执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件。
在这里插入图片描述

2.Channel和Unsafe

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

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()

3.ChannelPipeline和ChannelHandler、ChannelHandlerContext

  当 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,还可以用于写出站数据。

Channel、ChannelPipeline 和 ChannelHandlerContext 上的事件传播

  ChannelHandlerContext 有很多方法,其中一些方法也存在于 Channel 和ChannelPipeline 上,但有一点不同:如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用 ChannelHandlerContext上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的 ChannelHandler。
在这里插入图片描述
  比如服务器收到对端发过来的报文,解压后需要进行解密,结果解密失败,要给对端一个应答。
  如果发现解密失败原因是服务器和对端的加密算法不一致,应答报文只能以明文的压缩格式发送,就可以在解密 handler 中直接使用 ctx.write 给对端应答,这样应答报文就只经过压缩 Handler 发往对端;
  其他情况下,应答报文要以加密和压缩格式发送,就可以在解密 handler 中使用channel.write()或者 channelpipeline.write()给对端应答,这样应答报文就会流经整个出站处理过程。

ChannelHandlerContext API
  • alloc 返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator
  • bind 绑定到给定的 SocketAddress,并返回 ChannelFuture
  • channel 返回绑定到这个实例的 Channel
  • close 关闭 Channel,并返回 ChannelFuture
  • connect 连接给定的 SocketAddress,并返回 ChannelFuture
  • deregister 从之前分配的 EventExecutor 注销,并返回 ChannelFuture
  • disconnect 从远程节点断开,并返回 ChannelFuture
  • executor 返回调度事件的 EventExecutor
  • fireChannelActive 触发对下一个 ChannelInboundHandler 上的 channelActive()方法(已连接)的调用
  • fireChannelInactive 触发对下一个 ChannelInboundHandler 上的 channelInactive()方法(已关闭)的调用
  • fireChannelRead 触发对下一个 ChannelInboundHandler 上的 channelRead()方法(已接收的消息)的调用
  • fireChannelReadComplete 触发对下一个 ChannelInboundHandler 上的channelReadComplete()方法的调用
  • fireChannelRegistered 触发对下一个 ChannelInboundHandler 上的fireChannelRegistered()方法的调用
  • fireChannelUnregistered 触发对下一个 ChannelInboundHandler 上的fireChannelUnregistered()方法的调用
  • fireChannelWritabilityChanged 触发对下一个 ChannelInboundHandler 上的fireChannelWritabilityChanged()方法的调用
  • fireExceptionCaught 触发对下一个 ChannelInboundHandler 上的fireExceptionCaught(Throwable)方法的调用
  • fireUserEventTriggered 触发对下一个 ChannelInboundHandler 上的fireUserEventTriggered(Object evt)方法的调用
    handler 返回绑定到这个实例的 ChannelHandler
  • isRemoved 如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true
  • name 返回这个实例的唯一名称
  • pipeline 返回这个实例所关联的 ChannelPipeline
  • read 将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个channelRead 事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler 的channelReadComplete(ctx)方法
  • write 通过这个实例写入消息并经过 ChannelPipeline
  • writeAndFlush 通过这个实例写入并冲刷消息并经过 ChannelPipeline

  当使用 ChannelHandlerContext 的 API 的时候,有以下两点:

  • ChannelHandlerContext 和ChannelHandler 之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
  • 相对于其他类的同名方法,ChannelHandlerContext 的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能
ChannelHandler 接口

  Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。
  比如,ChannelInboundHandler 是一个经常实现的子接口。这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的业务逻辑所处理。当你要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 直接冲刷数据然后输出到对端。应用程序的业务逻辑通常实现在一个或者多个 ChannelInboundHandler 。
  这种类型的 ChannelHandler 接收入站事件和数据,这些数据随后将会被应用程序的业务逻辑所处理。

Netty 定义了下面两个重要的 ChannelHandler 子接口:

  • ChannelInboundHandler 处理入站数据以及各种状态变化;
  • ChannelOutboundHandler 处理出站数据并且允许拦截所有的操作。
ChannelHandler 适配器

  Netty 提供了抽象基类 ChannelInboundHandlerAdapter(处理入站) 和ChannelOutboundHandlerAdapter(处理出站)。
  ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 类作为自己的 ChannelHandler 的起始点。这两个适配器分别提供了 ChannelInboundHandler 和ChannelOutboundHandler 的基本实现。通过扩展抽象类 ChannelHandlerAdapter,它们获得了它们共同的超接口 ChannelHandler 的方法。
在这里插入图片描述
  ChannelOutboundHandler 有个 read 方法,不是表示读数据,而是表示业务发出了读(read)数据的要求,这个要求也会封装为一个事件进行传播,这个事件因为是业务发到网络,自然是出站事件,而且这个事件触发的就是ChannelOutboundHandler 中 read 方法。
  可以使用ChannelDuplexHandler类既处理入站又处理出站,也可以同时实现 ChannelOutboundHandler,ChannelInboundHandler 这两个接口。

Handler 的共享和并发安全性

  ChannelHandlerAdapter 提供了isSharable()方法。如果其对应的实现被标注为Sharable,那么这个方法将返回 true,表示它可以被添加到多个 ChannelPipeline。
  回顾 Netty 代码,在往 pipeline 安装 Handler 的时候,我们基本上是 new 出 Handler 的实例
  因为每个 socketChannel 有自己的 pipeline 而且每个 socketChannel 又是和线程绑定的,所以这些 Handler 的实例之间完全独立,只要 Handler 的实例之间不是共享全局变量,Handler 的实例就是线程安全的。
  但是如果业务需要我们在多个 socketChannel 之间共享一个 Handler 实例怎么办呢?比如统计服务器接受到和发出的业务报文总数,我们就需要用一个 Handler 的实例来横跨所有的 socketChannel 统计所有 socketChannel 业务报文数。
  可以实现一个 MessageCountHandler,并且在MessageCountHandler 上使用 Netty 的@Sharable 注解,然后在安装 MessageCountHandler 实例到 pipeline 时,共用一个即可。当然,因为 MessageCountHandler 实例是共享的,所以在实现 MessageCountHandler 的统计功能时,需要注意线程安全,在具体实现时可以使用 Java 并发编程里的 Atomic 类来保证这一点。
在这里插入图片描述

4.ChannelInitializer

  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 中移除它自己。
  在我们自己的应用中,如果存在着某个 handler 只使用一次的情况,也可以仿造 ChannelInitializer,用完以后将自己从 ChannelPipeline 中移除自己,比如授权 handler,某客户端第一次连接登录以后,进行授权检查,检查通过后就可以把这个授权 handler 移除了。如果客户端关闭连接下线,下次再连接的时候,就是一个新的连接,授权 handler 依然会被安装到 ChannelPipeline ,依然会进行授权检查。

5.ChannelOption

ChannelOption 的各种属性在套接字选项中都有对应。
ChannelOption.SO_BACKLOG
  ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,服务端处理客户端连接请求是顺序处理,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求就放在队列中等待处理。操作系统里一般有两个队列,一个是 ACCEPT 队列,保存已经完成 TCP 三次握手的连接,一个 SYN 队列,服务器正在等待 TCP 三次握手完成的队列。

ChannelOption.SO_REUSEADDR
  ChanneOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口,
比如,多网卡(IP)绑定相同端口,比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用。而且程序死掉以后,内核也需要一定时间才能释放此端口,不设置 SO_REUSEADDR 就无法正常使用该端口。
但是这个参数无法做到让应用绑定完全相同 IP + Port 来重复启动。

ChannelOption.SO_KEEPALIVE
  Channeloption.SO_KEEPALIVE 参数对应于套接字选项中的 SO_KEEPALIVE,该参数用于设置 TCP 连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的连接。当设置该选项以后,如果在两小时内没有数据的通信时,TCP 会自动发送一个活动探测数据报文。

ChannelOption.SO_SNDBUF 和 ChannelOption.SO_RCVBUF
  ChannelOption.SO_SNDBUF 参数对应于套接字选项中的 SO_SNDBUF,ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF 这两个参数用于操作接收缓冲区和发送缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功,发送缓冲区用于保存发送数据,直到发送成功。

ChannelOption.SO_LINGER
  ChannelOption.SO_LINGER 参数对应于套接字选项中的 SO_LINGER,Linux 内核默认处理方式是当用户调用 close()方法的时候,函数返回。在可能的情况下,尽量发送数据,不一定保证会发生剩余的数据,造成了数据的不确定性,使用 SO_LINGER 可以阻塞 close()的调用时间,直到数据完全发送。

ChannelOption.TCP_NODELAY
  ChannelOption.TCP_NODELAY 参数对应于套接字选项中的 TCP_NODELAY,该参数的使用与 Nagle 算法有关,Nagle 算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到来,组装成大的数据包进行发送。虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用 Nagle 算法,使用于小数据即时传输,于 TCP_NODELAY 相对应的是 TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

6.ByteBuf

ByteBuf API 优点

  • 可以被用户自定义的缓冲区类型扩展;
  • 通过内置的复合缓冲区类型实现了透明的零拷贝;
  • 容量可以按需增长(类似于 JDK 的 StringBuilder);
  • 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法;
  • 读和写使用了不同的索引;
  • 支持方法的链式调用;
  • 支持引用计数;
  • 支持池化。

  ByteBuf 维护了两个不同的索引,名称以 read 或者 write 开头的 ByteBuf 方法,将会推进其对应的索引,而名称以 set 或者 get 开头的操作则不会。
  如果打算读取字节直到 readerIndex 达到和 writerIndex 同样的值时,到达“可以读取的”数据的末尾,就如同试图读取超出数组末尾的数据一样,试图读取超出该点的数据将会触发一个 IndexOutOfBoundsException。
  可以指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触发一个异常。(默认的限制是 Integer.MAX_VALUE)

使用模式

堆缓冲区
  最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间。这种模式被称为支撑数组(backing array),它能在没有使用池化情况下提供快速的分配和释放。可以由 hasArray()来判断检查 ByteBuf 是否由数组支撑。如果不是,则是一个直接缓冲区。

直接缓冲区
  直接缓冲区是另外一种 ByteBuf 模式。
在这里插入图片描述
  直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。
  直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO一般会直接操作直接内存(直接内存->系统调用->硬盘/网卡),而非直接内存则需要二次拷贝(堆内存->直接内存->系统调用->硬盘/网卡)。

优点

  • 不占用堆内存空间,减少了发生GC的可能;
  • java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)

缺点

  • 相对于基于堆缓冲区,它们的分配和释放都较为昂贵。
  • 没有JVM直接帮助管理内存,容易发生内存溢出。为了避免一直没有FULL GC,最终导致直接内存把物理内存耗完。我们可以指定直接内存的最大值,通过-XX:MaxDirectMemorySize来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC,间接把那些没有被使用的直接内存回收掉。

复合缓冲区
  复合缓冲区 CompositeByteBuf,它为多个 ByteBuf 提供一个聚合视图。比如 HTTP 协议,分为消息头和消息体,这两部分可能由应用程序的不同模块产生,各有各的 ByteBuf,将会在消息被发送的时候组装为一个 ByteBuf,此时可以将这两个 ByteBuf 聚合为一个CompositeByteBuf,然后使用统一和通用的 ByteBuf API 来操作。

零拷贝

在这里插入图片描述
  Netty的接收和发送ByteBuf采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。
  如果使用传统的JVM堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才能写入Socket中。JVM堆内存数据是不能直接写入Socket中的。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

分配

ByteBufAllocator 接口
Netty 通过 interface ByteBufAllocator 分配我们所描述过的任意类型的 ByteBuf 实例。

  • buffer() 返回一个基于堆或者直接内存存储的 ByteBuf
  • heapBuffer() 返回一个基于堆内存存储的 ByteBuf
  • directBuffer() 返回一个基于直接内存存储的 ByteBuf
  • compositeBuffer() 返回一个可以通过添加最大到指定数目的基于堆的或者直接内存存储的缓冲区来扩展的 CompositeByteBuf
  • ioBuffer() 返回一个用于套接字的 I/O 操作的 ByteBuf,当所运行的环境具有 sun.misc.Unsafe 支持时,返回基于直接内存存储的 ByteBuf,否则返回基于堆内存存储的 ByteBuf;当指定使用PreferHeapByteBufAllocator 时,则只会返回基于堆内存存储的ByteBuf。

  可以通过 Channel(每个都可以有一个不同的 ByteBufAllocator 实例)或者绑定到ChannelHandler 的 ChannelHandlerContext 获取一个到 ByteBufAllocator 的引用。
  Netty 提供了两种 ByteBufAllocator 的实现:PooledByteBufAllocator 和Unpooled-ByteBufAllocator。前者池化了 ByteBuf 的实例以提高性能并最大限度地减少内存碎片。后者的实现不池化 ByteBuf 实例,并且在每次它被调用时都会返回一个新的实例。
  Netty4.1 默认使用了 PooledByteBufAllocator。

ByteBuf内存池设计
  随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区Buffer(相当于一个内存块),情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty提供了基于ByteBuf内存池的缓冲区重用机制。需要的时候直接从池子里获取ByteBuf使用即可,使用完毕之后就重新放回到池子里去。

Unpooled 缓冲区
  Netty 提供了一个简单的称为 Unpooled 的工具类,它提供了静态的辅助方法来创建未池化的 ByteBuf 实例。

  • buffer() 返回一个未池化的基于堆内存存储的 ByteBuf
  • directBuffer()返回一个未池化的基于直接内存存储的 ByteBuf
  • wrappedBuffer() 返回一个包装了给定数据的 ByteBuf
  • copiedBuffer() 返回一个复制了给定数据的 ByteBuf

  Unpooled 类还可用于 ByteBuf 同样可用于那些并不需要 Netty 的其他组件的非网络项目。

7.Future和Promise

   Netty 中所有的 I/O 操作都是异步的,我们知道“异步的意思就是不需要主动等待结果的返回,而是通过其他手段比如,状态通知,回调函数等”,那就是说至少我们需要一种获得异步执行结果的手段。
  JDK 预置了 interface java.util.concurrent.Future,Future 提供了一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符。它将在未来的某个时刻完成,并提供对其结果的访问。但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以 Netty 提供了它自己的实现ChannelFuture,用于在执行异步操作的时候使用。
  一般来说,每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture。

源码流程

在这里插入图片描述

Netty源码流程图





在这里插入图片描述

NIO源码流程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

paopaodog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值