网络编程之认识Netty

作者:xiaoxiyuan 文章内容输出来源:拉勾教育Java高薪训练营

本文主要内容包括:
Netty简介、Netty 高性能(零拷贝和支持高性能序列化协议等)、Netty线程模型、Netty粘包与拆包、Netty核心组件、Netty版自定义RPC案例实现、Netty源码剖析

1、Netty简介

1.1 Netty是什么?

Netty官网:https://netty.io/

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

Netty介绍:Netty 是一个提供异步的、基于事件驱动的网络编程框架,用以快速开发高性能的网络服务端和客户端。
说明:
异步:当一个异步进程调用发出之后,调用者不会立刻得到结果。而是在调用发出之后,被调用者通过状态、通知来通知调用者,或者通过回调函数来处理这个调用。
事件驱动:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件,即事件发生时才来处理事件。事件驱动方式也被称为消息通知方式,相对于轮询方式(线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑)表现为可扩展性好,在分布式的异步架构中,事件处理器之间高度解耦,可以方便扩展事件处理逻辑,其次表现出高性能,基于队列暂存事件,能方便并行异步处理事件。

1.2 Netty应用场景

(1)消息服务器,例如消息中间件RocketMQ底层用Netty作为通信框架;
(2)即时通讯(IM,Instant Messaging),例如在弹幕系统、游戏系统、直播群聊等当中即时发送和接收消息;
(3)RPC框架,例如阿里巴巴开源RPC框架(Dubbo) 也是使用Netty作为底层通信框架的;
(4)其他应用,例如ElasticSearch搜索引擎框架(github搜索)、Hadopp子项目Avro项目等,都是使用Netty作为底层通信框架的;

1.3 Netty组成

components.png

Core(核心模块):可扩展事件模型、通用通信API、支持零拷贝的ByteBuf缓冲对象;
Protocol Support(协议支持模块):HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议;
Transport Services(传输服务支持模块):BIO和NIO的socket服务、HTTP隧道服务等;

1.4 Netty解决的问题

Netty可以解决原生NIO存在的问题。
原生NIO存在的问题:
(1)NIO的类库和 API繁杂,使用麻烦。比如需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
(2)可靠性不强,开发工作量和难度都非常大。比如客户端面临断连重连、网络闪断、网络拥塞和异常流的处理等。
(3)JDK NIO 的 Bug:比如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。

2、Netty 高性能表现(优点)

(1)适应各种传输协议:对各种传输协议提供统一的 API;
(2)IO 线程模型:同步非阻塞,用最少的资源做更多的事;
(3)更好的吞吐量,更低的等待延迟;
(4)串形化处理读写:避免使用锁带来的性能开销;
(5)内存零拷贝:尽量减少不必要的内存拷贝,实现了更高效率的传输;
(6)内存池设计:申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况;
(7)高性能序列化协议:支持 protobuf 等高性能序列化协议;
扩展说明:
Netty零拷贝
【1】网络编程中零拷贝的理解
零拷贝是服务器网络编程的关键,任何性能优化都离不开。零拷贝从操作系统角度来说,是没有cpu 拷贝,在 Java 中,常用的零拷贝优化技术有 mmapsendFile
mmap优化:指通过内存映射,将文件映射到内核缓冲区,同时用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。
sendFile优化:Linux系统版本中提供了 sendFile 函数,其基本原理是:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时由于和用户态完全无关,就减少了一次上下文切换。
mmap 和 sendFile 的区别:
(1)mmap 适合小数据量读写,sendFile 适合大文件传输。
(2)mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
(3)sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。
在这个选择上:RocketMQ 在消费消息时,使用了 mmap;kafka 使用了 sendFile。
【2】Netty中零拷贝理解
(1)Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket读写,JVM会将堆内存 Buffer拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
(2)Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
(3)Netty 的文件传输采用了 transferTo 方法,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

Netty支持的高性能序列化协议
【1】序列化和反序列化
网络应用程序中,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码 。
序列化(编码)是将对象序列化为二进制形式(字节数组),主要用于网络传输、数据持久化等;
反序列化(解码)则是将从网络、磁盘等读取的字节数组还原成原始对象,主要用于网络传输对象的解码,以便完成远程调用。
影响序列化性能的关键因素:序列化后的码流大小(网络带宽的占用)、序列化的性能(CPU资源占用);是否支持跨语言(异构系统的对接和开发语言切换)。
【2】几种常见的序列化协议
Java默认提供的序列化:无法跨语言、序列化后的码流太大、序列化的性能差;
XML:优点:人机可读性好,可指定元素或特性的名称。缺点:序列化数据只包含数据本身以及类的结构,不包括类型标识和程序集信息;只能序列化公共属性和字段;不能序列化方法;文件庞大,文件格式复杂,传输占带宽。适用场景:当做配置文件存储数据,实时数据转换;
JSON,是一种轻量级的数据交换格式,优点:兼容性高、数据格式比较简单,易于读写、序列化后数据较小,可扩展性好,兼容性好、与XML相比,其协议比较简单,解析速度比较快。缺点:数据的描述性比XML差、不适合性能要求为ms级别的情况、额外空间开销比较大。适用场景(可替代XML):跨防火墙访问、可调式性要求高、基于Web browser的Ajax请求、传输数据量相对小,实时性要求相对低(例如秒级别)的服务;
Fastjson,采用一种“假定有序快速匹配”的算法。优点:接口简单易用、目前java语言中最快的json库。缺点:过于注重快,而偏离了“标准”及功能性、代码质量不高,文档不全。适用场景:协议交互、Web输出、Android客户端;
Thrift,不仅是序列化协议,还是一个RPC框架。优点:序列化后的体积小, 速度快、支持多种语言和丰富的数据类型、对于数据字段的增删具有较强的兼容性、支持二进制压缩编码。缺点:使用者较少、跨防火墙访问时,不安全、不具有可读性,调试代码时相对困难、不能与其他传输层协议共同使用(例如HTTP)、无法支持向持久层直接读写数据,即不适合做数据持久化序列化协议。适用场景:分布式系统的RPC解决方案;
Avro,Hadoop的一个子项目,解决了JSON的冗长和没有IDL的问题。优点:支持丰富的数据类型、简单的动态语言结合功能、具有自我描述属性、提高了数据解析速度、快速可压缩的二进制数据形式、可以实现远程过程调用RPC、支持跨编程语言实现。缺点:对于习惯于静态类型语言的用户不直观。适用场景:在Hadoop中做Hive、Pig和MapReduce的持久化数据格式。
Protobuf,将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的POJO对象和Protobuf相关的方法和属性。优点:序列化后码流小,性能高、结构化数据存储格式(XML JSON等)、通过标识字段的顺序,可以实现协议的前向兼容、结构化的文档更容易管理和维护。缺点:需要依赖于工具生成代码、支持的语言相对较少,官方只支持Java 、C++ 、python。适用场景:对性能要求高的RPC调用、具有良好的跨防火墙的访问属性、适合应用层对象的持久化;
【3】Google 的 Protobuf
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式 。目前很多公司 http+json 向 tcp+protobuf转变。Protobuf 是以 message 的方式来管理数据,支持跨平台、跨语言,即客户端和服务器端可以是不同的语言编写的(支持目前绝大多数语言,例如 C++、C#、Java、python 等),Protobuf具有高可靠性,使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述。在idea 中编写 .proto 文件时,会自动提示是否下载 .ptotot编写插件. 可以让语法高亮。然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件。
protocol-buffers的参考文档地址 : https://developers.google.com/protocol-buffers/docs/proto

3、Netty线程模型

传统的线程模型有:

单线程模型:
image-20200606155356691.png

线程池模型:
image-20200606155448556.png

Netty线程模型:


image-20200606155530496.png

Netty 抽象出两组线程池, BossGroup 专门负责接收客户端连接, WorkerGroup 专门负责网络读写操作。
NioEventLoop 表示一个不断循环执行处理任务的线程, 每个 NioEventLoop 都有一个 selector, 用于监听绑定在其上的 socket 网络通道。 NioEventLoop 内部采用串行化设计, 从消息的读取-->解码-->处理-->编码-->发送, 始终由 IO 线 程 NioEventLoop 负责。
说明:
(1)BossGroup 和 WorkerGroup 类型都是NioEventLoopGroup,NioEventLoopGroup 相当于一个事件循环组, 这个组中含有多个事件循环 ,每一个事件循环是 NioEventLoop;
(2)NioEventLoopGroup 可以有多个线程, 即可以含有多个NioEventLoop;
(3)每个Boss NioEventLoop 循环执行的步骤有3步:
​ 1.轮询accept 事件;
​ 2.处理accept 事件 , 与client建立连接 , 生成NioScocketChannel , 并将其注册到某个worker NIOEventLoop 上的 selector;
​ 3.处理任务队列的任务 ;
(4)每个 Worker NIOEventLoop 循环执行步骤有3步:
​ 1.轮询read, write 事件
​ 2.处理i/o事件, 即read , write 事件,在对应NioScocketChannel 处理
​ 3.处理任务队列的任务 ;
(5)每个Worker NIOEventLoop 处理业务时,会使用pipeline(管道), pipeline 中包含了 channel , 即通过pipeline 可以获取到对应通道, 管道中维护了很多的 处理器。

4、Netty粘包与拆包

4.1 TCP 粘包和拆包

TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个数据包更有效的发给接收端,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但因为面向流的通信是无消息保护边界的,这样接收端就难于分辨出完整的数据包了,可能发送的原来完整的包之间出现了粘合或完整的包接收到后就不完整(拆解),这种情况就是TCP粘包和拆包问题。

4.2 TCP粘包拆包图解

image-20200615102623300.png

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
● 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
● 服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包;
● 服务端分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这称之为TCP拆包;
● 服务端分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。

4.3 TCP粘包拆包现象实例及解决方案

在编写Netty 程序时,如果没有做处理,就会发生粘包和拆包的问题;
TCP粘包和拆包解决方案
(1)使用自定义序列化协议编解码器来解决
(2)关键就是要解决服务器端每次读取数据长度的问题, 这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的TCP 粘包、拆包 。
项目地址:https://gitee.com/tudedong/netty-tcp.git

5、Netty核心组件

【ChannelHandler 及其实现类】
ChannelHandler 接口定义了许多事件处理的方法, 我们可以通过重写这些方法去实现具体的业务逻辑;
我们经常需要自定义一个 Handler 类去继承 ChannelInboundHandlerAdapter, 然后通过重写相应方法实现业
务逻辑, 我们接下来看看一般都需要重写哪些方法:

- public void channelActive(ChannelHandlerContext ctx), 通道就绪事件
- public void channelRead(ChannelHandlerContext ctx, Object msg), 通道读取数据事件
- public void channelReadComplete(ChannelHandlerContext ctx) , 数据读取完毕事件
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause), 通道发生异常事件

Pipeline ChannelPipeline
ChannelPipeline 是一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于一个贯穿 Netty 的链。(可以这样理解:ChannelPipeline 是保存 ChannelHandler 的 List,用于处理或拦截Channel 的入站事件和出站操作);
ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何交互;
在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,组成关系图如下:

image-20200606171625810.png

说明:
一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler,入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。
常用方法有:

- ChannelPipeline addFirst(ChannelHandler... handlers), 把一个业务处理类(handler) 添加到链中的第一个位置
- ChannelPipeline addLast(ChannelHandler... handlers), 把一个业务处理类(handler) 添加到链中的最后一个位置

【ChannelHandlerContext】
ChannelHandlerContext 是 事 件 处 理 器 上 下 文 对 象 , Pipeline 链 中 的 实 际 处 理 节 点 。 每 个 处 理 节 点ChannelHandlerContext 中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler , 同 时
ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler 进行调用。
常用方法如下所示:

- ChannelFuture close(), 关闭通道
- ChannelOutboundInvoker flush(), 刷新
- ChannelFuture writeAndFlush(Object msg) , 将 数 据 写 到 ChannelPipeline 中 当 前
- ChannelHandler 的下一个 ChannelHandler 开始处理(出站)

ChannelOption
Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数。ChannelOption 参数如下:

ChannelOption.SO_BACKLOG
对应 TCP/IP 协议 listen 函数中的 backlog 参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog 参数指定了队列的大小。

ChannelOption.SO_KEEPALIVE
一直保持连接活动状态

【EventLoopGroup 和其实现类 NioEventLoopGroup】
EventLoopGroup 是一组 EventLoop 的抽象, Netty 为了更好的利用多核 CPU 资源, 一般会有多个 EventLoop
同时工作, 每个 EventLoop 维护着一个 Selector 实例。 EventLoopGroup 提供 next 接口, 可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。 在 Netty 服务器端编程中, 我们一般都需要提供两个
EventLoopGroup, 例如: BossEventLoopGroup 和 WorkerEventLoopGroup。
常用方法:

- public NioEventLoopGroup(), 构造方法
- public Future shutdownGracefully(), 断开连接, 关闭线程

【Selector】
Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件,当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel。

【Channel 和Future、ChannelFuture】
在 Netty 中所有的 I/O 操作都是异步的, I/O 的调用会直接返回, 调用者并不能立刻获得结果, 但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFuture,它们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件;
Channel 是Netty 网络通信的组件,能够用于执行网络 I/O 操作,通过Channel 可获得当前网络连接的通道的状态和网络连接的配置参数 (例如接收缓冲区大小),Channel 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成,调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常用的 Channel 类型:

NioSocketChannel,异步的客户端 TCP Socket 连接。
NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
NioDatagramChannel,异步的 UDP 连接。
NioSctpChannel,异步的客户端 Sctp 连接。
NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

ChannelFuture表示 Channel 中异步 I/O 操作的结果;
常用方法如下所示:

- Channel channel(), 返回当前正在进行 IO 操作的通道
- ChannelFuture sync(), 等待异步操作执行完毕

【Bootstrap 和 ServerBootstrap】
Bootstrap 是“引导”的意思,Bootstrap类 是 Netty 中的客户端动引导类, 通过它可以完成客户端的各种配置;ServerBootstrap类 是 Netty 中的服务器端启动引导类,通过它可以完成服务器端的各种配置; 它们的主要作用是配置整个 Netty 程序,串联各个组件;
常用方法如下所示:

- public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于服务器端, 用来设置两个 EventLoop
- public B group(EventLoopGroup group) , 该方法用于客户端, 用来设置一个 EventLoop
- public B channel(Class channelClass), 该方法用来设置一个服务器端的通道实现
- public  B option(ChannelOption option, T value), 用来给 ServerChannel 添加配置
- public  ServerBootstrap childOption(ChannelOption childOption, T value), 用来给接收到的通道添加配置
- public ServerBootstrap childHandler(ChannelHandler childHandler), 该方法用来设置业务处理类(自定义的 handler)
- public ChannelFuture bind(int inetPort) , 该方法用于服务器端, 用来设置占用的端口号
- public ChannelFuture connect(String inetHost, int inetPort) 该方法用于客户端,用来连接服务端

总结:
组件间的关系用服务的启动流程和请求的处理流程来说明下:
启动流程:无论是服务端还是客户端,先创建EventLoopGroup线程池组,用来接收请求或处理请求,后创建Bootstrap启动器辅助引导类,接着对Bootstrap进行初始化设置,包括创建SocketChannel即TCP连接Socket、创建ChannelPipeline、加入编解码器、加入ChannelHandlerContext 事 件 处 理 器 上 下 文 对 象、加入ChannelHandler事件处理器等, Bootstrap初始化设置完成后,绑定或连接服务地址,实现监听等;
请求处理流程:轮询监听过程,当一个请求过来时,经过SocketChannel,进入ChannelPipeline,然后把其中的每个ChannelHandler的逻辑都执行下,完成服务端和客户端间的交互。

6、Netty版自定义RPC案例实现

image-20200607135717933-1592273445777.png

案例项目地址:https://gitee.com/tudedong/zdy-netty-rpc.git

7、Netty源码剖析

Netty源码剖析将从Netty启动过程、Netty接受请求、三大核心组件ChannelPipeline 和ChannelHandler及ChannelHandlerContex的创建、ChannelPipeline调度handler、Netty心跳(heartbeat)机制、Netty 核心组件 EventLoop和handler中加入线程池和Context 中添加线程池七个方面进行说明。

7.1 Netty启动过程源码剖析

(1)创建2个 EventLoopGroup 线程池数组。数组默认大小CPU*2,方便选择线程池时提高性能;
(2)BootStrap 将 boss 设置为 group属性,将 worker 设置为 childer 属性;
(3)通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法;
(4)initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象, pipeline , unsafe,同时也为 pipeline 初始化了 head 节点和 tail 节点;
(5)在register0 方法成功以后调用在 dobind 方法中调用 doBind0 方法,该方法会 调用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,完成 Netty 服务器的所有启动,并开始监听连接事件。

7.2 Netty接受请求过程源码剖析

(1)接受连接,服务器轮询 Accept 事件,获取事件后调用 unsafe 的 read 方法,这个 unsafe 是 ServerSocket 的内部类,该方法内部由两部分组成;
(2)创建一个新的NioSocketChannel,即doReadMessages 用于创建 NioSocketChannel 对象,该对象包装 JDK 的 Nio Channel 客户端,该方法会像创建 ServerSocketChanel 类似创建相关的 pipeline , unsafe,config;
(3)注册到一个 worker EventLoop 上并 注册selecot Read 事件,随后执行 pipeline.fireChannelRead 方法,并将自己绑定到一个 chooser 选择器选择的 workerGroup 中的一个 EventLoop。并且注册一个0,表示注册成功,但并没有注册读(1)事件。

7.3 三大核心组件ChannelPipeline 、 ChannelHandler 和ChannelHandlerContext创建源码剖析

(1)每当创建 ChannelSocket 的时候都会创建一个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表;
(2)在调用 pipeline 的 addLast 方法的时候,会根据给定的 handler 创建一个 Context,然后将这个 Context 插入到链表的尾端(tail 前面);
(3)Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表,入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始。

7.4 ChannelPipeline调度handler的源码剖析

(1)Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表,入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始;
(2)而节点中间的传递通过 AbstractChannelHandlerContext 类内部的 fire 系列方法,找到当前节点的下一个节点不断的循环传播。是一个过滤器形式完成对handler 的调度。

7.5 Netty心跳(heartbeat)机制源码剖析

Netty 提供了 IdleStateHandler ,ReadTimeoutHandler,WriteTimeoutHandler 三个Handler 检测连接的有效性,具体内容如下:

名称 作用
IdleStateHandler 当连接的空闲时间(读或写)太长时,将会触发一个IdleStateEvent事件。然后,你可以通过你的ChannelInboundHandler中重写userEventTrigged方法来处理该事件。
ReadTimeoutHandler 如果在指定的事件没有发生读事件,就会抛出这个异常,并自动关闭这个连接。你可以在exceptionCaught方法中处理这个异常。
WriteTimeoutHandler 当一个写操作不能在一定的时间内完成时,抛出此异常,并关闭连接。你同样可以在exceptionCaught方法中处理这个异常。

7.6 Netty 核心组件 EventLoop源码剖析

EventLoop是一个单例线程池,里面含有一个死循环的线程不断的处理着3个逻辑:监听端口、处理端口事件、处理队列事件,每个EventLoop都可以绑定多个Channel,而每个Channel都只能有一个EventLoop来处理。

7.7 handler中加入线程池和Context 中添加线程池的源码剖析

在 Netty 中有很多耗时的,不可预料的操作,比如连接数据库,网络请求等,这些会严重影响 Netty 对 Socket 的处理速度;而解决方法就是将耗时任务添加到异步线程池中。但就添加线程池这步操作来讲,可以有两种方式:
(1)第一种方式:在handler 中加入线程池
(2)第二种方式:在Context 中添加线程池

文章内容输出来源:拉勾教育Java高薪训练营
若有错误之处,欢迎留言指正~~~

你可能感兴趣的:(网络编程之认识Netty)