Netty深入浅出
第一章 Netty——异步和事件驱动
1、引入——NIO和IO哪家强
1、1 传统阻塞IO连接方式及存在的问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65z9zhuQ-1640245095470)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210730171911124.png)]
阻塞IO,只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端Socket 创建一个新的 Thread,
即一个客户端对应一个Socket
=======》这就出了大问题
-
在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。
-
需要为每个线程的调用栈都分配内存,其默认值大小区间为 64 KB 到 1 MB,具体取决于操作系统。
-
即使 Java 虚拟机(JVM)在物理上可以支持非常大数量的线程,但是远在到达该极限之前,上下文切换所带来的开销就会带来麻烦
1、2 Netty中NIO的牛逼
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kab6cuA6-1640245095478)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210730171847139.png)]
转眼回到netty,两个字,牛逼
- 使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销
- 当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务
———————————————————————————————————————————————————————————————————————
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSYMCitz-1640245095481)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210730175353622.png)]
1.2、异步和事件驱动
-
可伸缩性:一种系统、网络或者进程在需要处理的工作不断增长时,可以通过某种可行的方式或者扩大它的处理能力来适应这种增长
的能力。
异步和可伸缩性的的联系
-
非阻塞网络调用使得我们可以不必等待一个操作的完成。完全异步的 I/O 正是基于这个特性构建的,并且更进一步:异步方法会立即返回,并且在它完成时,会直接或者在稍后的某个时间点通知用户。
-
选择器使得我们能够通过较少的线程便可监视许多连接上的事件。
-
1.3、Netty核心组件
-
channel
-
回调
-
Future
-
事件和ChannelHandler
这些构建块代表了不同类型的构造:资源、逻辑、通知。你的应用程序将使用它们来访问网络以及流经网络的数据。
1.3.1 Channel
它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作
1.3.2 回调
Netty 在内部使用了回调来处理事件;当一个新的连接已经被建立时,ChannelHandler 的 channelActive()回调方法将会被调用,并将打印出一条信息。
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx){
throws Exception {
System.out.println(
"Client " + ctx.channel().remoteAddress() + " connected");
}
}
1.3.3 Future
Future 提供了另一种在操作完成时通知应用程序的方式。
每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture,也就是说,IO操作不会被阻塞——Netty是异步事件驱动的。
异步建立连接
Channel channel = ...;
ChannelFuture future = channel.connect(
new InetSocketAddress("192.168.0.1", 25));
connect()方法将会直接返回,不会阻塞,该调用将会在后台完成。
如何利用 ChannelFutureListener。首先,要连接到远程节点上。然后,要注册一个新的 ChannelFutureListener 到对 connect()方法的调用所返回的ChannelFuture 上。当该监听器被通知连接已经建立的时候,要检查对应的状态 。如果该操作是成功的,那么将数据写到该 Channel。否则,要从 ChannelFuture 中检索对应的 Throwable。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXA5UhH5-1640245095483)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210802101429517.png)]
1.3.4 事件和 ChannelHandler
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。
-
这使得我们能够基于已经发生的事件来触发适当的动作。比如:
- 记录日志
- 数据转换
- 流控制
- 应用程序逻辑
-
由入站数据或者相关的状态更改而触发的事件
- 连接已被激活或者连接失活
- 数据读取
- 用户事件
- 错误事件
-
出站事件是未来将会触发的某个动作的操作结果
- 打开或者关闭到远程节点的连接
- 、将数据写到或者冲刷到套接字
一个事件是如何被ChannelHandler链处理的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1xm0ICh-1640245095484)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210802103235779.png)]
每个 ChannelHandler 的实例都类似于一种为了响应特定事件而被执行的回调。
Netty 通过触发事件将 Selector 从应用程序中抽象出来
第二章 如何构建一个基于 Netty 的客户端和服务器
2.1 编写服务器
多个客户端同时连接到一台服务器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7rBam9a-1640245095486)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210802114711574.png)]
2.2 编写Echo服务器
2.2.1 ChannelHandler和业务逻辑
因为你的 Echo 服务器会响应传入的消息,所以它需要实现 ChannelInboundHandler 接口,用来定义响应入站事件的方法。
我们感兴趣的方法是:
-
channelRead()—对于每个传入的消息都要调用;
-
channelReadComplete()—通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息;
-
exceptionCaught()—在读取操作期间,有异常抛出时会调用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0e5vB0Cn-1640245095489)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803092331218.png)]
可以在exception中重新连接端掉的连接 ,
如果不捕获异常,会发生什么呢每个 Channel 都拥有一个与之相关联的ChannelPipeline,其持有一个 ChannelHandler 的实例链。在默认的情况下,ChannelHandler 会把对它的方法的调用转发给链中的下一个 ChannelHandler。因此,如果 exceptionCaught()方法没有在该链中的某处实现,那么所接收的异常将会被传递到 ChannelPipeline 的尾端并被记录。为此,你的应用程序应该提供至少有一个实现了exceptionCaught()方法的 ChannelHandler。(6.4 节详细地讨论了异常处理)。
2.2.2 引导服务器
-
绑定到服务器将在其上监听并接受传入连接请求的端口;
-
配置 Channel,以将有关的入站消息通知给 EchoServerHandler 实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtrHQleP-1640245095491)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803133612356.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yADKeanE-1640245095492)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803133632619.png)]
服务器主要代码组件:
-
EchoServerHandler 实现了业务逻辑;
-
main()方法引导了服务器;
-
-
main()方法引导了服务器;
-
创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据
-
指定服务器绑定的本地的 InetSocketAddress;
-
使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
-
调用 ServerBootstrap.bind()方法以绑定服务器。
-
2.3 编写Echo客户端
客户端的功能:
- 连接到服务器
- 发送一个或多个消息
- 对于多个消息,等待并接受从服务器发回的相同的消息
- 关闭连接
2.3.1 通过 ChannelHandler 实现客户端逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7XHQArbD-1640245095493)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803140157428.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qkWF2nP0-1640245095494)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803140207868.png)]
你重写了 channelActive()方法,其将在一个连接建立时被调用。
你重写了 channelRead0()方法。每当接收数据时,都会调用这个方法。
2.3.2 引导客户端
客户端是使用主机和端口参数来连接远程地址,也就是这里的 Echo 服务器的地址,而不是绑定到一个一直被监听的端口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sReWalk8-1640245095495)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803142919233.png)]
- 为初始化客户端,创建了一个 Bootstrap 实例;
- 为进行事件处理分配了一个 NioEventLoopGroup 实例,其中事件处理包括创建新的连接以及处理入站和出站数据;
- 为服务器连接创建了一个 InetSocketAddress 实例;
- 当连接被建立时,一个 EchoClientHandler 实例会被安装到(该 Channel 的)ChannelPipeline 中;
- 在一切都设置完成后,调用 Bootstrap.connect()方法连接到远程节点;
第三章 Netty的组件和设计
3.1 Channel接口
基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提供的原语。
Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
3.2 EventLoop接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1iTWqCZ-1640245095497)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803144247542.png)]
-
一个 EventLoopGroup 包含一个或者多个 EventLoop;
-
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
-
所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
-
一个 Channel 在它的生命周期内只注册于一个 EventLoop;
-
一个 EventLoop 可能会被分配给一个或多个 Channel。
3.3 ChannelFuture接口
Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。
3.4 Channelhandler接口
Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。
channelHandler用来处理channnel上的各种事件,分为两种,一种是入站事件,一种是出站事件,所有的handler连成一串,就是pipeline
入站事件通常是ChannelInboundhandlerAdapter的子类,主要用来读取客户端数据,写回结果
出战事件通常是channelOutboundHandlerAdapter的子类,主要用来对鞋结果进行加工
3.5 ChannelPipeline 接口
ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。
ChannelHandler 安装到 ChannelPipeline 中的过程:
-
一个ChannelInitializer的实现被注册到了ServerBootstrap中 ①;
-
当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
-
ChannelInitializer 将它自己从 ChannelPipeline 中移除。
为什么需要适配器类
有一些适配器类可以将编写自定义的 ChannelHandler 所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现。
下面这些是编写自定义 ChannelHandler 时经常会用到的适配器类:
ChannelHandlerAdapter
ChannelInboundHandlerAdapter
ChannelOutboundHandlerAdapter
ChannelDuplexHandler
3.6 编码器和解码器
入站消息会被解 码;也就是说,从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,将从它的当前格式被编码为字节。
所有由 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口
3.7 抽象类 SimpleChannelInboundHandler
最常见的情况是,你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。要创建一个这样的 ChannelHandler,你只需要扩展基类 SimpleChannelInboundHandler,其中 T 是你要处理的消息的 Java 类型 。在这个 ChannelHandler 中, 你将需要重写基类的一个或者多个方法,并且获取一个到 ChannelHandlerContext 的引用,这个引用将作为输入参数传递给 ChannelHandler 的所有方法。在这种类型的 ChannelHandler 中,最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞当前的 I/O 线程之外,其具体实现完全取决于你。
第四章 传输
4.1 通过Netty使用OIO和NIO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGBDoeaQ-1640245095498)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803170931187.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxYjUxLa-1640245095500)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803171005186.png)]
4.2 传输 API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MG2Zfogm-1640245095501)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803171422108.png)]
每个 Channel 都将会被分配一个 ChannelPipeline 和 ChannelConfig。ChannelConfig 包含了该 Channel 的所有配置设置,并且支持热更新。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDcNHNjn-1640245095502)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803171738755.png)]
写数据并将其冲刷到远程节点的任务:
Netty 的 Channel 实现是线程安全的,因此你可以存储一个到 Channel 的引用,并且每当你需要向远程节点写数据时,都可以使用它,即使当时许多线程都在使用它。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3RktWv1-1640245095504)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803172430554.png)]
多个线程使用同一个channel
4.3 内置的传输
第五章 ByteBuf
直接内存VS堆内存
ByteBuf支持直接内存和堆内存
-
直接内存创建和销毁的代价比较昂贵,但是读写性能高(少一次内存复制),适合配合池化功能一起使用
-
直接内存对GC压力小,但是这部分额你存不受JVM垃圾回收机的管理,但是也要注意及时主动释放。
// 创建池化基于堆的ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
// 创建池化基于直接内存的ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
池化VS非池化
netty中对于创建比较慢的东西,可以使用池化的思想来管理。就像对于数据库的连接,使用连接池来优化,对于创建昂贵的,就预先创建好,等创建的时候直接就拿过来使用就行。·
池化的最大意义在于可以重用ByteBuf,优点有
-
没有池化,则每次都得创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加GC压力
-
有了池化,则可以重用池中的ByteBuf实例,并且采用了与jemalloc类似的内存分配算法提升分配效率
-
高并发时,池化功能更节约内存,减少内存溢出的可能
池化功能是否开启,可以通过下面的系统环境变量来设置
-Dio.netty.allocator.type={unpooled|pooled}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vgYt4io-1640245095505)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914145443569.png)]
4.1之后,非andriod平台默认启用池化实现,andriod平台启用非池化实现
4.1之前,池化功能还不成熟,默认是非池化实现
5.1 ByteBuf 的 API
-
它可以被用户自定义的缓冲区类型扩展;
-
通过内置的复合缓冲区类型实现了透明的零拷贝;
-
容量可以按需增长(类似于 JDK 的 StringBuilder);
-
在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法;
-
读和写使用了不同的索引;
-
支持方法的链式调用;
-
支持引用计数;
-
支持池化。
5.2 ByteBuf 类——Netty 的数据容器
5.2.1 ByteBuf的使用模式
-
堆缓冲区
最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEHJdXFH-1640245095506)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803201127339.png)]
-
直接缓冲区
NIO 在 JDK 1.4 中引入的 ByteBuffer 类允许 JVM 实现通过本地调用来分配内存。这主要是为了避免在每次调用本地 I/O 操作之前(或者之后)将缓冲区的内容复制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区)。
直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你正在处理遗留代码,你也可能会遇到另外一个缺点:因为数据不是在堆上,所以你不得不进行一次复制,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olmaDyxn-1640245095508)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803201813611.png)]
-
复合缓冲区
由两部分——头部和主体——组成的将通过 HTTP 协议传输的消息。这两部分由应用程序的不同模块产生,将会在消息被发送的时候组装。
不想为每个消息都重新分配这两个缓冲区时,可以使用CompositeByteBuf
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hz3ys3jV-1640245095509)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803202239462.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kXEfCYZB-1640245095510)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803202304057.png)]
上面的代码,分配和复制操作,以及伴随着对数组管理的需要,使得这个版本的实现效率低下而且笨拙。
下面的代码访问CompositeByteBuf 中的数据类似于(访问)直接缓冲区的模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2rnKCZ6-1640245095511)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803202527947.png)]
Netty使用了CompositeByteBuf来优化套接字的I/O操作,尽可能地消除了由JDK的缓冲区实现所导致的性能以及内存使用率的惩罚。这种优化发生在Netty的核心代码中,因此不会被暴露出来,但是你应该知道它所带来的影响。
5.3 字节级操作
ByteBuf 提供了许多超出基本读、写操作的方法用于修改它的数据。那就需要字节级操作
5.3.1 随机访问索引
如同在普通的 Java 字节数组中一样,ByteBuf 的索引是从零开始的:第一个字节的索引是0,最后一个字节的索引总是 capacity() - 1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqdU5cyJ-1640245095512)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803220309798.png)]
通过索引访问数据不会改变readerIndex和writeIndex
5.3.2 顺序访问索引
虽然 ByteBuf 同时具有读索引和写索引,但是 JDK 的 ByteBuffer 却只有一个索引,这也就是为什么必须调用 flip()方法来在读模式和写模式之间进行切换的原因。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IJEcoyT-1640245095513)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210803220616642.png)]
5.3.3 可丢弃字节
有需要再去看,在调用discardReadBytes()之后,对可写分段的内容并没有任何的保证。除非内存非常紧张,不然不建议使用。
5.3.4 可读字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KRjZDzZ-1640245095515)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804092434772.png)]
5.3.5 可写字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xtnvZzqz-1640245095517)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804095120095.png)]
5.3.6 索引管理
可以通过调用 markReaderIndex()、markWriterIndex()、resetWriterIndex()和 resetReaderIndex()来标记和重置 ByteBuf 的 readerIndex 和 writerIndex。
可以通过调用 readerIndex(int)或者 writerIndex(int)来将索引移动到指定位置。
通过调用 clear()方法来将 readerIndex 和 writerIndex 都设置为 0。并不会清除掉里面的内容
5.3.7 查找操作
- 最简单的是使用indexOf()方法。
- 较复杂的查找,ByteBufProcessor
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUdWrgzx-1640245095518)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804101944504.png)]
5.3.8 派生缓冲区
-
duplicate();
-
slice();
-
slice(int, int);
-
Unpooled.unmodifiableBuffer(…);
-
order(ByteOrder);
-
readSlice(int)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJPFTO0C-1640245095519)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102219739.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvlfnnzt-1640245095521)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102355499.png)]
5.3.9 读写操作
-
get()和 set()操作,从给定的索引开始,并且保持索引不变;
-
read()和 write()操作,从给定的索引开始,并且会根据已经访问过的字节数对索 引进行调整。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKpgcTrr-1640245095523)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102523542.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YD1dnFUu-1640245095525)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102551091.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJ2vxAlS-1640245095526)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102609068.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUPSYyPF-1640245095527)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102619878.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzXtgq24-1640245095529)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102712608.png)]
5.3.10 更多操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCoJzHEJ-1640245095530)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102809654.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Da768eHe-1640245095531)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102817783.png)]
5.4 ByteBufHolder 接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ab76zoq4-1640245095532)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804102909236.png)]
5.5 ByteBuf 分配
5.5.1 按需分配:ByteBufAllocator 接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ecJP0W6Y-1640245095533)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804103007269.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRvmg7oR-1640245095534)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804103034676.png)]
5.5.2 Unpooled 缓冲区
5.5.3 ByteBufUtil 类
hexdump()方法,它以十六进制的表示形式打印ByteBuf 的内容。
5.6 引用计数
引用计数背后的想法并不是特别的复杂;它主要涉及跟踪到某个特定对象的活动引用的数量。一个 ReferenceCounted 实现的实例将通常以活动的引用计数为 1 作为开始。只要引用计数大于 0,就能保证对象不会被释放。当活动引用的数量减少到 0 时,该实例就会被释放。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-js56VUYI-1640245095535)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804103951723.png)]
5.7扩容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3LZwBpj-1640245095537)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914150252251.png)]
5.8 retain&release
由于netty中有堆外内存的ByteBuf实现,堆外内存最好时手动释放,而不是等GC垃圾回收
- UnpooledheapByteBuf使用的是JVM的内存,只需要等GC回收内存即可
- UnpooledFrectByteBuf使用的是直接内存,需要特殊的方法来回收内存
- PooledByteBuf和他的子类使用了池化机制,需要更复杂的规则来回收内存。
回收内存的源码实现,请关注下面的不同实现
protected absrtract void deallocate()
Netty这里采用了引用计数法来控制回收内存,每个ByteBuf都实现了ReferenceCounted接口
-
每个ByteBuf对象的初始计数为
-
调用release方法计数减1,如果计数为0,ByteBuf内存被回收
-
调用retain方法计数加1,表示调用者没用完之前,其它 handler即使调用了release 也不会造成回收·
-
当计数为0时,底层内存会被回收,这时即使ByteBuf对象还在,其各个方法均无法正常使用
那我们该如何释放内存呢?
ByteBuf buf = ...
try{
...
}finally{
buf.release();
}
那我们是在使用完ByteBuf后就释放ByteBuf吗,并非如此
我们来看一下pipeline组成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCwtVPcj-1640245095540)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914151542012.png)]
因为pipeline的存在,一般需要将ByteBuf传递给下一个ChannelHandler,如果在finally 中 release了,就失去了传递性(当然,如果在这个ChannelHandler内这个ByteBuf已完成了它的使命,那么便无须再传递)
因此,释放ByteBuf应该是在最后一个handler或者说是最后一次使用ByteBuf的时候进行释放
附:head和tail中会释放一次,但是不等于就不用手动去释放了,因为有些情况就穿不到head或者tail中
5.9 slice
【零拷贝】的体现之—,对原始ByteBuf进行切片成多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始ByteBuf的内存,切片后的ByteBuf维护独立的read,write 指针
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKzJsCty-1640245095541)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914153041198.png)]
例如,原始ByteBuf进行一些初始操作
ByteBuf origin = ByteBufAllocator . DEFAULT. buffer (10);
origin.writeBytes(new byte[]{1, 2,3,4});
origin.readByte();
System.out.print1n(ByteBufuti1.prettyHexDump(origin)) ;
5.10 duplicate
【零拷贝的体现之一】,就好比截取了原始的ByteBuf所有内容,并且没有maxcapacity的限制,也是与原始的ByteBuf使用同一块底层内存,只是读写指针是独立的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MrWPvMw2-1640245095543)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914160509885.png)]
5.11 copy
会将底层内存进行深拷贝,因此无论读写,都与原始的ByteBuf无关
5.12 unpooled
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeyhK4Fv-1640245095544)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210914162123481.png)]
读写误解
我最初在认识上有这样的误区,认为只有在netty, nio 这样的多路复用I0模型时,读写才不会相互阻塞,才可以实现高效的双向通信,但实际上,Java Socket是全双工的:在任意时刻,线路上存在A到B和B到A的双向实现高效的双向通信,但实际上,Java Socket是全双工的:在任意时刻,线路上存在A到B和B到A的双向信号传输。即使是阻塞I0,读和写是可以同时进行的,只要分别采用读线程和写线程即可,读不会阻塞写、写也不会阻塞读。
服务器和客户端并非要实现一问一答通信模式。
第 六章 ChannelHandler和ChannelPipeline
6.1 ChannelHandler
6.1.1 Channel 的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h1tZ8yiT-1640245095545)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804104408512.png)]
6.1.2 ChannelHandler 的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G9DGy8PW-1640245095547)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804104507977.png)]
Netty 定义了下面两个重要的 ChannelHandler 子接口:
-
ChannelInboundHandler——处理入站数据以及各种状态变化;
-
ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。
6.1.3 ChannelInboundHandler 接口
这些方法将会在数据被接收时或者与其对应的 Channel 状态发生改变时被调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6E0tEB4-1640245095548)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804111510235.png)]
@Sharable 注解用来说明ChannelHandler是否可以在多个channel直接共享使用。
释放消息资源。当所有可读的字节都已经从 Channel 中读取之后,将会调用该回调方法;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BY76g8ay-1640245095550)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804141425876.png)]
6.1.4 ChannelOutboundHandler 接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sDAjaEyV-1640245095551)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804141919142.png)]
6.1.5 ChannelHandler 适配器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EAomeWLk-1640245095553)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804142148329.png)]
6.1.6 资源管理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBDgYh3Q-1640245095554)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804143520505.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUk2bCXS-1640245095555)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804143529594.png)]
6.2 ChannelPipeline 接口
为ChannelPipeline是一个拦截流经Channel的入站和出站事件的ChannelHandler 实例链
每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的。在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。
6.2.1 修改 ChannelPipeline
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DfwVlwA4-1640245095557)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804145243319.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5azw4CF-1640245095558)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804145251072.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FuEhVUE5-1640245095559)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804145446068.png)]
6.2.2 触发事件
6.3 ChannelHandlerContext 接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oqxtAyDa-1640245095560)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804151503304.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Va3CwKkc-1640245095561)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804151515283.png)]
6.3.1 使用 ChannelHandlerContext
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1g0HhTl-1640245095562)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804151621281.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5kqFoXa-1640245095564)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804151638304.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lswgUNfV-1640245095565)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804151650313.png)]
要想调用从某个特定的 ChannelHandler 开始的处理过程,必须获取到在(ChannelPipeline)该ChannelHandler 之前的 ChannelHandler 所关联的 ChannelHandlerContext。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyXnVovV-1640245095567)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804155852275.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-osc2EU5y-1640245095568)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804160048193.png)]
6.4 异常处理
6.4.1 处理入站异常
要想处理这种类型的入站异常,你需要在你的 ChannelInboundHandler 实现中重写下面的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVqRJkcW-1640245095570)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804161912207.png)]
6.4.2 处理出站异常
处理出战操作,基于以下通知机制:
-
每个出站操作都将返回一个 ChannelFuture。注册到 ChannelFuture 的 ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了。
-
几乎所有的 ChannelOutboundHandler 上的方法都会传入一个 ChannelPromise的实例。作为 ChannelFuture 的子类,ChannelPromise 也可以被分配用于异步通知的监听器。
添加 ChannelFutureListener 只需要调用 ChannelFuture 实例上的 addListener(ChannelFutureListener)方法,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PqW51DdM-1640245095573)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804163147643.png)]
第二种方式:将 ChannelFutureListener 添加到即将作为参数传递给 ChannelOutboundHandler 的方法的 ChannelPromise。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQg6C5gY-1640245095574)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804163235620.png)]
第 七章 EventLoop和线程模型
7.1 线程模型概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c98ecFFv-1640245095575)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804163933615.png)]
7.2 EventLoop 接口
一个 EventLoop 将由一个永远都不会改变的 Thread 驱动,同时任务(Runnable 或者 Callable)可以直接提交给 EventLoop 实现,以立即执行或者调度执行。根据配置和可用核心的不同,可能会创建多个 EventLoop 实例用以优化资源的使用,并且单个EventLoop 可能会被指派用于服务多个 Channel。
7.3 任务调度
一个常见的用例是,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该 Channel 了。
7.3.1 JDK 的任务调度 API
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AiXRxZR6-1640245095576)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804165900924.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4qfTOYO-1640245095577)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170116866.png)]
7.3.2 使用 EventLoop 调度任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ukso5mEp-1640245095579)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170235007.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzJNCVw3-1640245095580)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170353624.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yWw0O8w-1640245095583)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170421229.png)]
7.4 实现细节
7.4.1 线程管理
-
异步传输
异步传输实现只使用了少量的 EventLoop(以及和它们相关联的 Thread),而且在当前的线程模型中,它们可能会被多个 Channel 所共享。
一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的 Thread)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B5m3yQ3n-1640245095590)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170554321.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9tPm3fMi-1640245095592)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170639309.png)]
2.阻塞传输
这里每一个 Channel 都将被分配给一个 EventLoop(以及它的 Thread)。
每个 Channel 的 I/O 事件都将只会被一个 Thread(用于支撑该 Channel 的 EventLoop 的那个 Thread)处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGJzEpUK-1640245095595)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804170923424.png)]
第八章 引导
8.1 Bootstrap 类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y4OwK9Dd-1640245095597)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210805133135332.png)]
8.2 引导客户端和无连接协议
8.2.1 引导客户端
第九章 单元测试——EmbeddedChannel
9.1 测试入站消息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZznoIuko-1640245095599)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210804172001782.png)]
第十章 序列化数据
10.1 通过 Protocol Buffers 序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HaVgBNVA-1640245095602)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210805112059003.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3u0iPn7-1640245095604)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210805112231514.png)]
Netty 实战
1、为什么netty要使用异步的方法,netty异步提升的是什么
下面看一个例子
但是这并不是netty的所作所为
要点:单线程没法异步提高效率,必须配合多线程,多核cpu才能发挥异步的优势。
异步没有缩短响应时间,反而有所增加。
合理进行任务拆分,也是利用异步的关键。
响应时间不减反增,那提升的是什么呢,提升的是单位时间内数据的吞吐量。
误区:netty不是因为多线程,所以效率就高
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tUQTOcTS-1640245095617)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826110344302.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzqJ5yVy-1640245095619)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826112041508.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMkUYFe3-1640245095621)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826144326830.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BweO1e3V-1640245095623)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826142126944.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWh9mYzG-1640245095625)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826143610168.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TjiDyGgQ-1640245095627)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826170852557.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T5M37wQ9-1640245095628)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210826171153700.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PgRwIqJQ-1640245095631)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210830155609739.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTDBzPFU-1640245095633)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210830161052740.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YBDrTtB5-1640245095634)(C:\Users\ASVS\AppData\Roaming\Typora\typora-user-images\image-20210831093948577.png)]
register把
附录 netty在nginx上的连接配置
当系统发布jar包的时候,前后端都部署在nginx中,nginx的配置如下
协议的设计与解析
由于现有的协议,有一些缺点,比如多个数据之间使用回车进行分割 ,字节数不够紧凑。 但是由于本身需求的业务比较复杂,因而可以自行设计一些协议,字节数更紧凑效率更高的协议。
自定义协议要素
-
魔数,用来在第一时间判定是否是无效数据包-------比如:java的二进制字节码,起始八个字节就是coffebaby
-
版本号,可以支持协议的升级
-
序列化算法,消息正文到底采用哪种序列化和反序列化的方式,可以由此扩展,例如:json,protobuf、hessian、jdk
-
指令类型、是登录、注册、单聊、群聊、…还是业务相关
-
请求序号、为了双工通信、提升异步能力
-
正文长度
-
消息正文---------------一般采用一种特定模式,常见的有json,对象流,xml…以前会使用xml,也可以使用二进制模式(对象流)
优化与源码
1.优化
1.1 扩展序列化算法
序列化,反序列化主要用在消息正文的转换上
- 序列化时,需要将java对象变为要传输的数据(可以是byte[],或json等,最终都需要变成byte[])
- 反序列化时,需要将传入的正文数据还原成java对象,便于处理
java自带的 序列化和 反序列化 机制,核心代码
//反序列化
byte[] body = new byte[bodyLength];
byteBuf.readBytes(body);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(body));
Message message = (Message)in.readObject();
message.setSequenceId(sequenceId);
//序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
new ObjectOutputStream(out).writeObject(message);
byte[] bytes = out.toByteArray();
1.2参数调优
1).CONNECT_TIMEOUT_MILLIS
- 属于SocketChannel参数
- 用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出timeout异常
- SO_TIMEOUT主要用在阻塞IO,阻塞IO中accept,read等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间
new BootStrap()
.option()
2).SO_BACKLOG
3).ulimit-n ------->服务器如果要应对高并发,支持大量的连接,那么服务器就要调整这个参数--------建议放在启动脚本里
- 属于操作系统参数
4)TCP_NODELAY --------如果是false,就启动nagle算法,这样会导致一些数据延迟(nagle会把小的数据包攒到一起发送,那么就可能会等待,导致延迟 --------如果是true,则会没有延迟传送(建议使用true) )
- 属于SocketChannel参数
5)SO_SNDBUF&SO_RCVBUF --------尽量不要去调整这个参数,netty会自动帮我们选择适合的-------自己调整可能反而画蛇添足。
- SO_SNDBUF属于SocketChannel参数
- SO_RCVBUF即可用于SocketChannel参数,也可以用于ServerSocketChannel参数(建议设置在ServerSocketChannel上)
6)ALLOCATOR
- 属于SocketChannel参数
- 用来分配ByteBuf,ctx.alloc()
7)RCVBUF_ALLOCATOR
- 属于SocketChannel参数
11