欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Netty—网络的高层建筑

程序员文章站 2022-05-22 20:34:28
...

Netty什么?

高性能、可扩展、异步事件驱动的网络应用程序框架,其极大的简化了TCP,UDP客户端和服务器端开发等网络编程,下面是最近学习的一些总结,希望有错误的大家提出来一起进步。

Netty架构—过程分析

  • Netty缓存区buffer
  • 通道channel、pipeline
  • selector机制的使用
    说明:这几个是比较重要的关键字,首先需要记住它们进行下面的阅读。
  1. 线程模型
    线程模型的不同,对性能的影响也是非常大的
    事件驱动模型
    通常我们设计一个事件处理模型的程序有两种思路:
    第一种:轮询方式,线程不断的询问相关发生源有没有发生事件,有发生事件就调用事件逻辑处理
    第二种:事件驱动方式:发生事件,主线程把请求事件放入等待事件队列Reactor线程池,另外任务线程不断循环消费者事件(请求任务),调用事件对应的处理逻辑处理事件,事件驱动也被称为消息通知方式,本质就是观察者的Reactor线程模式,模型如下:

Netty—网络的高层建筑
基本组件介绍:
1.事件队列 :储存待处理的事件(EventLoop轮询的请求队列)
2.事件分发器:把不同事件分配给不同的业务处理逻辑单元(Dispatcher)
3.事件通道: 事件分发器与事件处理器之间的连接
4.事件处理器 处理分发器分发过来的事件
说明:请求事件进入队列—>通过轮询事件分发器—>进过事件通道—>事件处理器

  • 取决于Reactor的数据以及handler的线程数量的不同,Reactor有三种线程模式;
    1.单Reactor单线程模型:
    2.单Reactor 多线程模型:
    3.主从Reacor多线程模型

说明: Reactor 模式也叫 Dispatcher 模式,即 I/O 多路复用统一监听事件,收到事件后分发(Dispatch 给某进程)分handler处理器进行处理。

  • 2 个关键组成:
  1. Reactor,Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。
  2. Handlers,处理程序执行 I/O 事件要完成的实际事件,一般是非阻塞操作。
    Reactor模型:

可以这样理解,Reactor 就是一个执行 while (true) { selector.select(); …} 循环的线程,会源源不断的产生新的事件,称作反应堆很贴切。

  • 主从Reacor多线程模型详解:
  1. MainReactor 负责客户端的连接请求,并将请求转交给 SubReactor。
  2. SubReactor 负责相应通道的 IO 读写请求。
  3. 非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。

特别说明的是:虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中,都是线程。

  • 异步处理:
    Netty进行异步操作时,比如bind,write,Connect等时,会返回一个channelFuture对象,调用者可以通过返回的对象channelFuture的状态,注册监听函数来执行完成后的操作

  • 具体future的方法有:

  1. isDone,表示完成
  2. isSuccess表示成功
  3. getCause 获取操作失败的原因
  4. isCancelled判断当前操作是否被取消
    说明:通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。

Netty架构—组件分析

  • 以下几方面分析Netty的架构设计
  • 模块组件:
  1. bootStrap:一个netty程序通常由bootstrap开始,配置整个netty,串联整个组件,客户端引导类
  2. selector:
    selector对象实现I/O多路复用技术,通过selector一个线程可以监听多个channel事件
    向 Selector 中注册 Channel ,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件,这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。
    serverBoorStrap:服务器端引导类
  3. 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 任务的执行时间相等
  4. NioEventLoopGroup
    主要EnventLoop的周期,可以理解为一个线程池,内部维护了一组线程,每个线程NioEventLoop负责处理多个channel上的事件,而一个channel对应一个线程
  5. Channel:
    不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应下面是一些常用的 Channel 类型:
  • NioSocketChannel,异步的客户端 TCP Socket 连接。
  • NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
  • NioDatagramChannel,异步的 UDP 连接。
  • NioSctpChannel,异步的客户端 Sctp 连接。
  • NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
  1. ChannelHandler
    ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
    ChannelInboundHandler 用于处理入站 I/O 事件。
    ChannelOutboundHandler 用于处理出站 I/O 操作。
    或者
    ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
    ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
    ChannelDuplexHandler 用于处理入站和出站事件。

  2. ChannelHandlerContext
    保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

  3. ChannelPipline
    保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。
    ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。
    在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline(一个包含多个channelHandler的list) 与之对应,
    一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。

  4. 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 + "]绑定失败!"); 
 } 
 }); 
  • 步骤:
  1. 初始化创建 2 个 NioEventLoopGroup,其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。
  2. 基于 ServerBootstrap(服务端启动引导类),配置 EventLoopGroup、Channel 类型,连接参数、配置入站、出站事件 handler。
    绑定端口,开始工作。
  3. 分析NioEventLoopGroup。
    NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。
  • 执行请求过程:

Netty—网络的高层建筑

  • 绑定端口过程:
    Netty—网络的高层建筑

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架构学习问题

  1. Netty中采用了哪些设计模式?
  2. 为什么我们在学习Netty的过程中主要是针对我们的服务器端开发而不是我们的客户端开发?