Netty Reactor 模式解析

目录

Reactor 模式        

具体流程

配置 

初始化

NioEventLoop 

ServerBootstrapAcceptor 分发


Reactor 模式        

在刚学 Netty 的时候,我们肯定都很熟悉下面这张图,它就是单Reactor多线程模型。

在写Netty 服务端代码的时候,下面的代码时必不可少的,这是为什么呢?

public static void main(String[] args) {
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup(4);
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup)
              .channel(NioServerSocketChannel.class)
              .childHandler(new NettyServerHandler(), new NettyServerHandler2());
    System.out.println("netty server start...");
    bootstrap.bind(9000);
}

        在 Netty 里,EventLoopGroup 就是线程池,不论 bossGroup 还是 workerGroup,它们里面的线程都叫 EventLoop。

        EventLoopGroup 就是一个线程池,bossGroup 叫连接线程池,它一般只有一个线程,workerGroup 叫工作线程池,它一般会有多个线程。bossGroup 线程池里的线程专门监听客户端连接事件,监听是否有 SelectionKey.OP_ACCEPT 事件被触发,所以 1 个线程就够用了,当它监听到有客户端请求连接时,它会把这个连接交给 workerGroup 里的一个线程去处理,这个过程叫分发,这个工作线程会为这个客户端建立一个 NIOSocketChannel,并注册到这个工作线程绑定的IO多路复用选择器 Selector 里,一个Selector可以接受多个 NIOSocketChannel 的注册,所以一个工作线程可以处理多个客户端。   这就是Reactor 模式,一个工作线程可以处理多个客户端,比 Java 传统的一个客户端对应一个工作线程节约了很多线程,减少了大量线程创建,线程切换,线程销毁的开销,所以Netty 性能很好。

        上面短短的服务端代码做了很多工作,当它刚启动还没有客户端请求连接时,bossGroup 连接线程池里的一个线程 EventLoop 会初始化一个 NioServerSocketChannel ,并把这个Channel注册到这个EventLoop 持有的IO多路复用选择器Selector里,Selector 会监听Channel里的 SelectionKey.OP_ACCEPT 事件,一旦有客户端连接过来,它会通过下面代码获取到一个

SocketChannel ch = javaChannel().accept();

NioSocketChannel,并把这个 NioSocketChannel 注册到 workerGroup 工作线程池里的一个EventLoop 里,它使用了一个叫 ServerBootstrapAcceptor 的 ChannelInboundHandler接口类去完成这个过程,连接完成后,后续这个客户端和服务端的交互和数据读写都在这个 EventLoop 完成。

具体流程

        下面我们看一下代码,Netty 代码中使用了很多继承,在继承中可以把子类相同的部分代码提到父类去完成,很多子类生成初始化的时候,它会调用父类的构造方法去完成,这个要注意。

配置 

        下面的代码主要做一些启动器的配置,group(bossGroup, workerGroup) 会设置连接线程池和工作线程池,后面有连接事件或读事件过来要处理时,它会从这些线程池里取线程去执行;channel(NioServerSocketChannel.class) 指定要生成服务端Channel,它只会监听SelectionKey.OP_ACCEPT 事件,childHandler(new NettyServerHandler(), new NettyServerHandler2()) 是我们业务处理的逻辑。

bootstrap.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new NettyServerHandler(), new NettyServerHandler2());

初始化

        bind() 会把 ServerBootstrapAcceptor 添加到 NioServerSocketChannel 的 pipeline ,它会处理连接;获取一个 bossGroup 线程池里的 EventLoop并和 NioServerSocketChannel  进行绑定。

bootstrap.bind(9000)
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> {
    public void bind(int inetPort) {
        doBind(new InetSocketAddress(inetPort));
    } 
    // bind 流程
    private void doBind(final SocketAddress localAddress) {
        initAndRegister();
        // 让channel绑定的线程处理
        channel.eventLoop().execute(()->{
            // 绑定指定端口
            channel.bind(localAddress);
        });
    } 
    // 初始化和注册
    final void initAndRegister() {
        init(channel);
        // 把 NioServerSocketChannel 注册到一个复杂连接事件的 EventLoop 的 Selector 里 
        group.register(channel);
    } 
    // 把 ServerBootstrapAcceptor 添加到 NioServerSocketChannel 的 pipeline 里
    abstract void init(Channel channel);
}

NioEventLoop 

        现在要说一下 NioEventLoop,它拥有一个IO多路复用选择器 Selector,这个线程会在一个死循环里工作,永远也会停止;这个线程它会先执行一下 selector.select(1000),阻塞监听1秒,看看有没有Channel有事件过来,有就去处理任务,没有就等待1秒钟再超时放弃,再看看自己的任务队列有没有可执行的任务,有就去处理任务,没有就继续进行死循环,继续执行 selector.select(1000)。无论是连接线程还是工作线程都这样处理,因为它们共用了这套逻辑。

public class NioEventLoop extends SingleThreadEventLoop {
    @Override
    protected void run() {
        for (;;) {
            try {
                select();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                # 处理事件
                processSelectedKeys();
            } finally {
                runAllTasks();
            }
        }
    }
    private void select() throws IOException {
        // 拿到多路复用器
        Selector selector = this.selector;
        for (;;) {
            // 等待,简化固定1秒
            int selectedKeys = selector.select(1000);
            // 如果有事件发生或当前有任务跳出循环
            if (selectedKeys != 0 || hasTasks()) {
                break;
            }
        }
    }
}

像下面这种 channel.eventLoop().execute(Runnable), 它也只是把 Runnable 加入到任务处理队列,稍后执行。

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> { 
    private void doBind(final SocketAddress localAddress) {
        ...
        channel.eventLoop().execute(()->{ 
            channel.bind(localAddress);
        });
    }  
}
public abstract class SingleThreadEventExecutor implements Executor {    
    // 待执行任务队列
    private final Queue<Runnable> taskQueue;   
    @Override
    public void execute(Runnable task) {
        // 把任务添加到 EventLoop 的任务队列,EventLoop 是 SingleThreadEventExecutor 的子类
        addTask(task);
        // 执行 EventLoop 的 run 逻辑
        startThread();
    }
}

        当 NioServerSocketChannel.accept() 监听到一个客户端连接,它会把这个 NIOSocketChannel 通过 pipeline 处理,最终被 ServerBootstrapAcceptor 所处理,

public class NioServerSocketChannel extends AbstractNioMessageChannel {
    @Override
    protected int doReadMessages(List<Object> buf) {
        SocketChannel ch = null;
        try {
            ch = javaChannel().accept();
        } catch (IOException e) {
        }
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
        return 0;
    }
}
public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
    @Override
    public void read() {
        final ChannelPipeline pipeline = pipeline();
        doReadMessages(readBuf);
        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
    }

    protected abstract int doReadMessages(List<Object> buf);
}

ServerBootstrapAcceptor 分发

        ServerBootstrapAcceptor 管理 workerGroup 里的所有工作线程和所有的业务处理代码 ChannelHandler,ServerBootstrapAcceptor 会把所有的 ChannelHandler 放到刚刚监听得到的 NIOSocketChannel 里的 pipeline 里,并从 workerGroup 里选择一个 EventLoop 工作线程把NIOSocketChannel 注册到该 EventLoop 拥有的IO多路复用选择器 Selector 里去,这就完成了分发,它已经处理了连接,后续这个 NIOSocketChannel 里的所有读写事件都会被 Selector 监听到,并被该 EventLoop 工作线程所处理。

private static class ServerBootstrapAcceptor implements ChannelInboundHandler {
        // 工作线程池,即 workerGroup 
        private final EventLoopGroup childGroup;
        // 业务操作 Handler
        private final ChannelHandler[] childHandlers;

        private ServerBootstrapAcceptor(EventLoopGroup childGroup, ChannelHandler[] childHandlers) {
            this.childGroup = childGroup;
            this.childHandlers = childHandlers;
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            final Channel child = (Channel) msg;
            // 完成 pipeline 责任链模式的组装
            for (ChannelHandler childHandler : childHandlers) {
                child.pipeline().addLast(childHandler);
            }
            // 把Channel 注册到 Selector
            childGroup.register(child);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            // 略
        }
    }
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/346474.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

『OpenCV-Python|鼠标作画笔』

Opencv-Python教程链接&#xff1a;https://opencv-python-tutorials.readthedocs.io/ 本文主要介绍OpenCV-Python如何将鼠标作画笔绘制圆或者矩形。 示例一&#xff1a;图片上双击的位置绘制一个圆圈 首先创建一个鼠标事件回调函数&#xff0c;鼠标事件发生时就会被执行。鼠标…

php 文件上传

目录 1 php.ini 配置文件的修改 2.系统返回码详解 错误级别 4.上传简单示例 5.php代码简单优化 1 php.ini 配置文件的修改 配置项说明file_uploads on 为 开启文件上传功能&#xff0c; off 为关闭 post_max_size 系统允许的 POST 传参的最大值 &#xff0c;默认 8M upl…

(二十八)ATP应用测试平台——使用electron集成vue3桌面应用程序

前言 Electron 是一个开源的框架&#xff0c;它允许使用 Web 技术&#xff08;HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序。通过 Electron&#xff0c;开发者可以使用前端技术栈来创建具有原生应用程序体验的桌面应用。 Electron可以在 Windows、Mac 和 L…

爬虫是什么 怎么预防

爬虫是一种自动化程序&#xff0c;用于从网页或网站中提取数据。它们通过模拟人类用户的行为&#xff0c;发送HTTP请求并解析响应&#xff0c;以获取所需的信息。 爬虫可以用于各种合法用途&#xff0c;如搜索引擎索引、数据采集和监测等。然而&#xff0c;有些爬虫可能是恶意的…

【Web前端实操11】定位实操_照片墙(无序摆放)

设置一个板块&#xff0c;将照片随意无序摆放在墙上&#xff0c;从而形成照片墙。本来效果应该是很唯美好看的&#xff0c;就像这种&#xff0c;但是奈何本人手太笨&#xff0c;只好设置能达到照片墙的效果就可。 代码如下&#xff1a; <!DOCTYPE html> <html lang&…

使用dcdiag 和 netdiag确保域控的复制和网络是健康的

dcdiag 和 netdiag 是 Windows 操作系统中的两个命令行工具&#xff0c;主要用于诊断和验证活动目录&#xff08;Active Directory&#xff09;环境的健康状况&#xff0c;包括复制、连接以及其他网络服务。 以下是如何运行这两个工具的步骤&#xff1a; 运行 dcdiag&#xf…

PMP考试刷题记录20240125

1、所有干系人都在开会讨论一个新项目&#xff0c;该项目预计将在一个月内启动&#xff0c;并持续至少10次迭代&#xff0c;其中一个干系人提到应该有人负责开发和维护产品路线图。谁应该承担这个责任? A.项目经理 B.开发团队 C.ScrumMaster D.产品负责人 答案&#xff1…

推荐HuoCMS多站点多语言CMS系统源码

HuoCMS是一套内容管理系统同时也是一套企业官网建设系统&#xff0c;能够帮过用户快速搭建自己的网站。可以满足企业站&#xff0c;外贸站&#xff0c;个人博客等一系列的建站需求。HuoCMS的优势: 可以使用统一后台管理多个网站的内容&#xff0c;统一维护&#xff0c;不同内容…

[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

​第20课 在Android Native开发中加入新的C++类

​这节课我们开始利用ffmpeg和opencv在Android环境下来实现一个rtmp播放器&#xff0c;与第2课在PC端实现播放器的思路类似&#xff0c;只不过在处理音视频显示和播放的细节略有不同。 1.压缩备份上节课工程文件夹并修改工程文件夹为demo20&#xff0c;将demo20导入到Eclipse或…

使用Linux SDK客户端向AWS Iot发送数据

参考链接&#xff1a; https://ap-southeast-1.console.aws.amazon.com/iot/home?regionap-southeast-1#/test 此篇文章用于测试&#xff0c;使用Linux SDK客户端向AWS Iot发送数据&#xff0c;准备环境如下&#xff1a; 1、客户端环境准备 1.1 客户端操作系统 虚拟机一台…

上门服务小程序|预约上门服务系统开发有哪些功能?

在现代快节奏的生活中&#xff0c;压力和疲劳常常困扰着我们。为了缓解这种状况&#xff0c;越来越多的人选择去按摩店进行放松。然而&#xff0c;繁忙的工作和家庭责任往往让我们无法抽出时间去按摩店。在这种情况下&#xff0c;上门按摩服务应运而生。而随着科技的发展&#…

Java 集合Map相关面试题

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于java面试题系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基…

ifconfig 主机ip url记录

ifconfig 容器Pods相关主机与url信息 一文搞懂网络知识&#xff0c;IP、子网掩码、网关、DNS、端口号_关于ip,网关。端口-CSDN博客 计算机网络知识之URL、IP、子网掩码、端口号_ip地址和url-CSDN博客 阅读看下以上文章 由此可知 1.主机ip 10.129.22.124 10.129.22 是网段…

业余爱好-生物信息学/生物化学/物理/统计学/政治/数学/概率论/AI/AGI/区块链

生物信息学 高等数学—元素和极限-实数的定义高等数学—元素和极限-实数的元素个数高等数学—元素和极限-自然数个数少于实数个数高等数学—元素和极限-无穷大之比较高等数学—元素和极限-级数的收敛高等数学—元素和极限-极限的定义数学分析与概率论人工智能AI数学基础——全套…

《向量数据库指南》——Milvus Cloud向量数据库的新认知

除了数字上的里程碑,2023 年业务模式的改变也带来了很多定性的认知。这些认知帮助我们深化了对向量这种数据类型的理解,也引导了我们思考向量数据库未来的发展方向。 大模型应用仍处于初期阶段:避免重蹈智能手机时代“手电筒应用”的覆辙 回顾移动互联网早期,许多开发者创…

纯前端实现了Excel文件转JSON和JSON转Excel下载

需求前提&#xff1a; 上传Excel文件&#xff0c;并将Excel文件的内容拿出来转换为JSON本地定义JSON数据&#xff0c;然后将它封装后转换为Excel文件下载 安装依赖 这两个功能是借助xlsx包实现的&#xff0c;所以需要先安装xlsx包&#xff1a; npm install xlxs依赖引用 i…

Ranger概述及安装配置

一、前序 希望拥有一个框架,可以管理大多数框架的授权,包括: hdfs的目录读写权限各种大数据框架中的标的权限,列级(字段)权限,甚至行级权限,函数权限(UDF)等相关资源的权限是否能帮忙做书库脱敏Ranger框架应运而生。 二、Ranger 2.1、什么是ranger Apache Ranger…

【动态规划】【map】【C++算法】1289. 下降路径最小和 II

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 map LeetCode1289. 下降路径最小和 II 给你一个 n x n 整数矩阵 grid &#xff0c;请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下降路径 定义为&#xff1a;从 grid 数组中的每一行选择一个数字&#xff0c;…

编写servlet

编写servlet 上述代码中的HTML页面将雇员ID发送给servlet。要创建servlet读取客户机发送的雇员ID并检索雇员的详细信息,需要执行以下步骤: 在“项目”选项卡中右击“Employee”节点,然后选择“新建”→Servlet。将显示“新建Servlet”对话框。在“类名”文本框中输入Employ…