Netty—网络的高层建筑
Netty什么?
高性能、可扩展、异步事件驱动的网络应用程序框架,其极大的简化了TCP,UDP客户端和服务器端开发等网络编程,下面是最近学习的一些总结,希望有错误的大家提出来一起进步。
Netty架构—过程分析
- Netty缓存区buffer
- 通道channel、pipeline
- selector机制的使用
说明:这几个是比较重要的关键字,首先需要记住它们进行下面的阅读。
- 线程模型
线程模型的不同,对性能的影响也是非常大的
事件驱动模型
通常我们设计一个事件处理模型的程序有两种思路:
第一种:轮询方式,线程不断的询问相关发生源有没有发生事件,有发生事件就调用事件逻辑处理
第二种:事件驱动方式:发生事件,主线程把请求事件放入等待事件队列Reactor线程池,另外任务线程不断循环消费者事件(请求任务),调用事件对应的处理逻辑处理事件,事件驱动也被称为消息通知方式,本质就是观察者的Reactor线程模式,模型如下:
基本组件介绍:
1.事件队列 :储存待处理的事件(EventLoop轮询的请求队列)
2.事件分发器:把不同事件分配给不同的业务处理逻辑单元(Dispatcher)
3.事件通道: 事件分发器与事件处理器之间的连接
4.事件处理器 处理分发器分发过来的事件
说明:请求事件进入队列—>通过轮询事件分发器—>进过事件通道—>事件处理器
- 取决于Reactor的数据以及handler的线程数量的不同,Reactor有三种线程模式;
1.单Reactor单线程模型:
2.单Reactor 多线程模型:
3.主从Reacor多线程模型
说明: Reactor 模式也叫 Dispatcher 模式,即 I/O 多路复用统一监听事件,收到事件后分发(Dispatch 给某进程)分handler处理器进行处理。
- 2 个关键组成:
- Reactor,Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。
- Handlers,处理程序执行 I/O 事件要完成的实际事件,一般是非阻塞操作。
Reactor模型:
可以这样理解,Reactor 就是一个执行 while (true) { selector.select(); …} 循环的线程,会源源不断的产生新的事件,称作反应堆很贴切。
- 主从Reacor多线程模型详解:
- MainReactor 负责客户端的连接请求,并将请求转交给 SubReactor。
- SubReactor 负责相应通道的 IO 读写请求。
- 非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。
特别说明的是:虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中,都是线程。
-
异步处理:
Netty进行异步操作时,比如bind,write,Connect等时,会返回一个channelFuture对象,调用者可以通过返回的对象channelFuture的状态,注册监听函数来执行完成后的操作 -
具体future的方法有:
- isDone,表示完成
- isSuccess表示成功
- getCause 获取操作失败的原因
- isCancelled判断当前操作是否被取消
说明:通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。
Netty架构—组件分析
- 以下几方面分析Netty的架构设计
- 模块组件:
- bootStrap:一个netty程序通常由bootstrap开始,配置整个netty,串联整个组件,客户端引导类
- selector:
selector对象实现I/O多路复用技术,通过selector一个线程可以监听多个channel事件
向 Selector 中注册 Channel ,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件,这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。
serverBoorStrap:服务器端引导类 - NioEnventLoop:
NioEnvenLoop维护一个线程和任务队列,可以异步提交任务,启动线程时调用NioEnventLoop的run方法,实行I/O或者非I/O
I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。
非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。
两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等 - NioEventLoopGroup
主要EnventLoop的周期,可以理解为一个线程池,内部维护了一组线程,每个线程NioEventLoop负责处理多个channel上的事件,而一个channel对应一个线程 - Channel:
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应下面是一些常用的 Channel 类型:
- NioSocketChannel,异步的客户端 TCP Socket 连接。
- NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
- NioDatagramChannel,异步的 UDP 连接。
- NioSctpChannel,异步的客户端 Sctp 连接。
- NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
-
ChannelHandler
ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
ChannelInboundHandler 用于处理入站 I/O 事件。
ChannelOutboundHandler 用于处理出站 I/O 操作。
或者
ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
ChannelDuplexHandler 用于处理入站和出站事件。 -
ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。 -
ChannelPipline
保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。
ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。
在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline(一个包含多个channelHandler的list) 与之对应,
一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。 -
channelFuture
在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。
但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。
Netty架构—代码启动分析
初始化并启动 Netty 服务端过程如下:
public static void main(String[] args) {
// 创建mainReactor
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
// 创建工作线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
// 组装NioEventLoopGroup
.group(boosGroup, workerGroup)
// 设置channel类型为NIO类型
.channel(NioServerSocketChannel.class)
// 设置连接配置参数
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
// 配置入站、出站事件handler
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
// 配置入站、出站事件channel
ch.pipeline().addLast(...);
ch.pipeline().addLast(...);
}
});
// 绑定端口
int port = 8080;
serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
} else {
System.err.println("端口[" + port + "]绑定失败!");
}
});
- 步骤:
- 初始化创建 2 个 NioEventLoopGroup,其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。
- 基于 ServerBootstrap(服务端启动引导类),配置 EventLoopGroup、Channel 类型,连接参数、配置入站、出站事件 handler。
绑定端口,开始工作。 - 分析NioEventLoopGroup。
NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。
- 执行请求过程:
- 绑定端口过程:
Netty架构—主从Reacor多线程执行过程
-
每个 MainReactor NioEventLoop 循环执行的任务包含 3 步
1.轮询accept
2.建立连接,生成NioSocketChannel,并注册到SubReactor的selector上
3.处理任务中的队列,runAllTasks -
每个SubReactor NioEventLoop 循环执行的任务包含 3 步:
1.轮询read,write事件,
2.处理I/O事件,在NioSocketchannel可读可写时处理read write,
3.处理任务队列中的任务 -
任务队列中的任务有三种典型使用场景
1.用户自定义的普通任务
2.用户自定义的定时任务
3.非当前Reactor线程调用channel的各种方法
Netty架构学习问题
- Netty中采用了哪些设计模式?
- 为什么我们在学习Netty的过程中主要是针对我们的服务器端开发而不是我们的客户端开发?