深入剖析 Netty 的核心组件
Posted GitChat精品课
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入剖析 Netty 的核心组件相关的知识,希望对你有一定的参考价值。
本篇文章对 Netty 中开发者最经常打交道的五个组件:ByteBuf,Channel,pipeline,ChannelHandler、EventLoop 做了详细的说明和使用讲解。掌握了这五个组件,就可以开始使用 Netty 编写网络应用了。
不过 Netty 带给我们的除了框架上的简化,也在于其异步化的编程模式。其异步编程模式与其背后的线程模型息息相关。本文我们将从以下几点,来深入剖析 Netty 的核心组件。
01
ByteBuf
ByteBuffer
。
ByteBuffer
的体验着实不太好,读写状态的区别,还有
flip
这种乍看下不直观的操作。
ByteBuf
。
和
ByteBuffer
一样,
ByteBuf
也是代表了一段连续的二进制数据空间。同样的,
ByteBuf
也按照数据存储的位置区分为:数据存储在堆上的
HeapByteBuf
和数据存储在直接内存的
DirectByteBuf
。
ByteBuf
的设计目标是简化用户的使用,所以不像
ByteBuffer
那样还有读写状态的区分,
ByteBuf
当中只有 2 个指针:读指针和写指针。读指针指向的位置,意味着可以从这个位置开始读取;写指针指向的位置,意味着可以将数据写入指向位置。
ByteBuf
带来的便利还不止如此,
ByteBuf
还具备自动扩容的能力。
在 Netty 中申请一个ByteBuf
都会指定一个初始容量,但是在写入的时候,如果剩余容量不足,则会自动扩容
。扩容规则为:
-
当写入后新的容量小于 512,则选择一个小于 512 但是大于容量且为 16 的倍数的值作为新容量。 -
当写入后新的容量大于 512,则选择一个大于容量且为 2 的次方幂的值作为新容量。
ByteBuf
比上面说的要复杂的许多,我们来看看其相关的部分类图
ByteBuf
是一个很庞大的继承体系。初略分的话大致是两个维度:
数据存储在堆和数据存储在直接内存的区别
-
ByteBuf
持有的内存区域是一次性的依靠 JVM 进行 GC,还是池化的内存依靠 Netty 自行管理的区别
ByteBuf
。
PoolDirectByteBuf
。
不过在编写代码的时候我们并不会直接实例化这个类。而是通过
io.netty.buffer.ByteBufAllocator
这个接口的
buffer
方法来获得具体的实例。
可以通过io.netty.buffer.ByteBufAllocator#DEFAULT
属性来获得系统默认的分配器。初始化的Netty会进行判断,如果当前是 android,则使用非池化的分配器;其余情况使用池化的分配器。对于服务器应用而言,池化分配器肯定是不二选择。
02
CompositeByteBuf
ByteBuf
是一个具备零拷贝能力的富
ByteBuffer
实现。
ByteBuf
也的确提供了
ByteBuffer
更多的功能,但是零拷贝本身而言,对于
DirectByteBuf
和
DirectByteBuffer
而言底层都是相同的,都是使用了堆外内存本身的零拷贝的特性。不过 Netty 还有额外提供了自己实现的零拷贝特性的
ByteBuf
,它是一个虚拟组合式的
ByteBuf
视图,也就是这个章节的主角:
CompositeByteBuf
。
在应用程序的编写过程中,部分场景下存在着需要将多个ByteBuf合并的需求。此时简单的使用io.netty.buffer.ByteBuf#writeBytes(io.netty.buffer.ByteBuf)
接口可以完成需求,不过就是需要额外的内存拷贝。
针对这种需要聚合多个ByteBuf的使用场景,Netty设计了CompositeByteBuf
类。这个类代表着一个虚拟的ByteBuf
,其内部是由多个ByteBuf
实例组成的数组。每一个ByteBuf
都代表着虚拟Buffer中的某一段数据。
举个例子,如下便是由三个ByteBuf
实例组成的CompositeByteBuf
。
ByteBuf
实例分别映射了虚拟Buffer不同区域的部分。
CompositeByteBuf
通过聚合的方式,对外提供了一个整体的Buffer的效果。
ByteBuf
进行存放,因此其解析方式不同的原因。
而当我们需要组装一个完整的Http报文的时候,如果将代表协议头和报文体的ByteBuf
实例一起写到一个新的ByteBuf
自然是可以满足需求,不过也带来了数据拷贝的消耗。此时使用CompositeByteBuf
作为一个虚拟视图聚合2个ByteBuf
,既能避免内存拷贝,又可以在对用户表现上呈现出一个完整单一的ByteBuf
的效果。提升了开发效率和性能。
Channel
io.netty.channel.socket.nio.NiosocketChannel:这个实现类一般是在网络编程中,引导程序帮助我们实例化的,而且实例化的时候传递给我们也是接口
io.netty.channel.socket.SocketChannel
,并不会让我们感知到这个具体的实现。这个类代表着一个具体的 TCP 通道。-
io.netty.channel.socket.nio.NioServerSocketChannel:这个实现类提供的是 TCP 协议下服务端监听 Socket 通道的能力。这个实现类一般直接将类对象传递给引导程序用于启动一个基于 TCP 协议的服务端。 io.netty.channel.epoll.EpollServerSocketChannel:一般而言,Java 的服务端应用都是部署在 Linux 服务器上。在 Linux 环境上,Netty 提供了自己实现的,更为高效的基于 Epoll 实现方式的 IO 复用实现。在引导程序中将
NioServerSocketChannel
替换为EpollServerSocketChannel
可以得到更高的性能。
-
情况一:引导程序初始化完毕后,获得引导程序实际创建的 Channel 对象,等待其关闭;通过这个等待可以实现优雅退出以及在服务端关闭后执行一些业务逻辑。 情况二:在客户端链接通过
io.netty.channel.ChannelInitializer#initChannel(C)
方法被创建后,获得通道的管道对象pipeline
,在其中添加处理器。-
情况三:持有通道对象,通过通道对象来将数据写出。
以上是关于深入剖析 Netty 的核心组件的主要内容,如果未能解决你的问题,请参考以下文章