Netty源码小窥探(一)

news2024/11/14 18:19:54

记录背景

最近在学习Netty,阅读了部分源码,记录一下笔记,方便自己回顾和也希望能作为初学的小伙伴们的部分参考。

本次Netty源码小窥探会是一个小合集,因为个人能力有限,介绍程度肯定不会太深奥,个人是基于尚硅谷的Netty课程进行的学习,在最后的源码讲解的基础上加入了自己的理解。有错误请见谅

适用人员

会Netty的基础使用的小伙伴

正文

引入

分析Netty的源码需要跟踪到NioServerSocketChannel的doBind方法,因为对于连接的监听,还是底层自然还是通过NioServerSocketChannel来监听获得NioSocketChannel的,所以这里是必须的;然后是NioEventLoop中的run方法,我们知道事件组就是一个无限循环的过程,也就是说服务端和客户端其实就是在NioEventLoop中循环执行,内部有selector进行事件区分,所以事件组中的内容也需要看。

首先我们来看一下简答配置的,标准的一个Netty服务端搭建案例代码:


public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.  对SSL进行相关配置
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.  开始和我们之前一样,正常配置服务端
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new LoggingHandler(LogLevel.INFO));
                     //p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

对于能使用基础使用Netty的开发人员而言,这段代码的难度肯定不大,我们本次的源码介绍就是根据这段代码顺序进行讲接,从而给大家介绍Netty底层的相关设计。

NioEventLoopGroup的子线程数目决定

对于我们整个Netty项目而言,无论是服务端还是客户端其本质上都是一个在内部循环执行的事件组,每当有对应的逻辑处理,就会事件组中拿出一个线程执行,这也是Netty并发和异步的基础。

对于服务端而言,Netty的连接模式是基于主从Reactor多线程,连接请求和一般逻辑请求处理是分开的,所以我们会需要建立两个循环事件组分别处理连接事件和其余事件,在Netty中是用NioEventLoopGroup数据类型来表示循环事件组的,所以我们一般会创建bossGroup来处理连接事件,workerGroup来处理其余事件(在Netty源码中,一般把bossGroup称之为parentGroup,WorkerGroup称之为ChildGroup)

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

NioEventLoopGroup是个事件循环组,内部会分配子线程,来处理事件,比如bossGroup,内部很多个就绪状态下的子线程,每当有一个客户端来请求连接,就会分配一个子线程去处理连接请求;如果同时来多个,就会将多个空闲就绪子线程分配去分别处理连接请求,那么这里有存在一个问题,NioEventLoopGroup内部的子线程数目是如何决定的呢?

在这里我们可以看到,我们创建bossGroup和WorkerGroup分别使用了有参和无参构造,我们进去查看源码就能知道子线程数据决定的逻辑了。

首先进入无参构造函数

如下图 当我们使用空参构造的时候 内部会传入nThread=0的参数 从而调用内部的有参构造函数

所以实际上我们还是去看有参构造函数

有参构造

从有参构造的结构一直往下,会运行到父类的MultithreadEventLoopGroup方法,代码和调用逻辑如下

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

在MultithreadEventLoopGroup方法的逻辑中我们可以看到,有参无参影响到的就是nThread这个参数,内部实际上就是将有参构造的nThreads参数设置为0,然后跟踪往下走,就会发现方法走到最后构造的时候,会单独判断nThreads是否为0,如果为0就是调用一个默认值,这个默认是在static静态代码块中看到数值为当前设备CPU核数*2

所以总的而言,NioEventLoopGroup内部的子线程数目可以通过构造函数传参数决定,无参构造的话默认为当前设备CPU核数*2,因为连接处理请求一般只会有一次,但是其余事件处理请求会有很多次,所以一般我们将bossGroup进行有参构造,设置一定的量满足需求即可,而对于WorkerGroup我们一般无参构造,充分发挥多核CPU优势,更加高效的处理。

构造方法和EventExecutor

我们上面探寻子线程数目是追踪到了父类的MultithreadEventLoopGroup方法,方法内部也是一个对父类方法的super调用,那我们继续往下追踪父类的构造方法中查看如下:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        checkPositive(nThreads, "nThreads");

        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

方法很长,我们慢慢看,这次先看前面的关于子线程的创建部分,很明显的看到成员变量children 也能想到这个变量就是我们所关注的关于子线程创建的变量

可以看到,代码中对于children的第一次赋值就是执行了下面代码

 this.children = new EventExecutor[nThreads];

我们知道,children就是NioEventLoopGroup的子线程,从这里的赋值我们可以看到,实际上children就是一个new出来创建的EventExceutor类型的数组,数组长度就是有参构造传入的nThread值或者static静态代码块中决定的数值 ,这也说明了为啥传参决定nThread能决定子线程的多少,子线程就是EventExceutor数组统一包含管理,数组的长度nThread就决定了子线程的多少。

我们走到下面for循环中 可以看到里面有对每一个元素的赋值语句 走到这个语句之后,我们可以看到,children确实是EventExecutor数组类型,但是每一个元素是NioEventLoop类型,为啥NioEventLoop类型能装到EventExcutor类型数组中呢?

我们点开EventExecutor类型 可以看到他是一个接口,然后查看他的继承结构可以看到

NioEventLoopGroup是EventExecutor的子接口的子接口的是实现类的子类,所以可以装进去

然后我们可以看到 每一个子线程的创建 都是依赖于newChild方法 方法中传入的第一个参数就是executor执行器

children[i] = newChild(executor, args);

同时也可以看到 在finally代码块中,会通过标志success进行判断是否创建成功,如果不成功就会调用shutdownGracefully方法关闭线程,并且也会拿到数组中的每一个元素EventExecutor e 对其调用awaitTerminated方法进行处理

while (!e.isTerminated()) { e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); } 这部分代码的作用是确保事件执行器(EventExecutor)已经完全终止。

具体来说:

  1. while (!e.isTerminated()):这是一个循环,它会一直执行,直到e.isTerminated()返回trueisTerminated()方法用来检查事件执行器是否已经终止。

  2. e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS):这个方法是用来等待事件执行器终止。它接受两个参数,分别是等待的最大时间和时间单位。这里使用Integer.MAX_VALUE作为等待时间,意味着如果事件执行器没有在自然停止的情况下终止,方法将无限期等待。

这段代码的目的在于:

  • 确保在某个事件执行器创建失败后,已经成功创建的事件执行器都能够正确地关闭。
  • 在关闭过程中,如果事件执行器因为某些原因未能正常终止,通过awaitTermination方法进行等待,确保所有资源都被正确释放,避免资源泄露。

并且在该方法的后面一点 可以看到 对children线程数组进行循环,对每一个元素使用addListener方法添加了监听器,方便后续事件监听

添加完了监听器之后,我们还可以看到,源码将EveentExecutor数组转化为了一个LinkedHashSet集合的形式进行保存,基于LinkedHashSet的特性,可以保证按照数组顺序链式保存,并且去重

readonlyChildren = Collections.unmodifiableSet(childrenSet);最后,这行代码将childrenSet转换成了一个不可修改的集合。Collections.unmodifiableSet方法返回的集合不允许添加、删除或者修改其中的元素。这意味着一旦这个集合被创建,它的内容就不能被改变了。这通常用于安全地发布集合,使得集合的消费者不能更改集合的内容,从而保护数据的一致性和安全性。将结果赋值给自己的成员Set集合变量

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记录|链接MySQL数据库实现学生管理系统

目录 前言一、管理系统的后台数据库1.1 创建DB库1.2 创建login表和student表login表student表 二、窗体创建1.1 VS项目框架1.2 窗口1设计&#xff1a;登录窗口所需控件界面效果 1.3 窗口2设计&#xff1a;学生信息管理界面添加新界面所需控件界面效果 1.4 窗口3设计&#xff1a…

用低代码平台搭建了一套贷前进件系统,1天搞定!

贷前进件系统是什么 贷前进件系统是指金融机构&#xff08;如银行、消费金融公司等&#xff09;在贷款申请过程中&#xff0c;用于收集、整理、审核和管理借款人申请资料的系统。该系统是贷款流程中的重要环节&#xff0c;主要作用在于提高贷款申请的处理效率&#xff0c;降低…

滑动变阻器的未来发展趋势和前景如何?是否有替代品出现?

滑动变阻器是常见的电子元件&#xff0c;主要用于调节电路中的电阻值。随着科技的不断发展&#xff0c;滑动变阻器的未来发展趋势和前景也引起了广泛关注。 滑动变阻器的未来发展将更加注重智能化&#xff0c;随着物联网、人工智能等技术的快速发展&#xff0c;滑动变阻器也将与…

Transformer中的位置编码:绝对位置编码、相对位置编码与旋转位置编码

1. 引言 Transformer模型自2017年提出以来&#xff0c;凭借其在序列到序列任务中的优异表现&#xff0c;迅速成为自然语言处理&#xff08;NLP&#xff09;领域的主流模型。与传统的循环神经网络&#xff08;RNN&#xff09;不同&#xff0c;Transformer模型完全基于自注意力机…

Java 入门指南:反射机制

RTTI RTTI&#xff08;Run-Time Type Identification&#xff09;是一种在运行时确定对象类型的机制。它是一种编程语言特性&#xff0c;主要用于在运行时动态识别和处理对象的实际类型。 通过 RTTI&#xff0c;可以在运行时根据对象的实际类型执行相应的操作。这在处理多态对…

sdn nce insight

SDN&#xff08;Software-Defined Networking&#xff09; 和 NFV&#xff08;Network Functions Virtualization&#xff09; 是现代网络架构中的两项关键技术&#xff0c;它们共同推动了网络的虚拟化、自动化和灵活性。尽管它们各自有不同的焦点和应用领域&#xff0c;但通常…

HarmonyOS应用四之页面加载构建以及数据请求

目录&#xff1a; 1、加载网络页面/本地页面/html页面2、页面布局3、HTTP/HTTPS的数据请求4、上传图片并保存数据 1、加载网络页面/本地页面/html页面 // xxx.ets import { webview } from kit.ArkWeb; import { BusinessError } from kit.BasicServicesKit;Entry Component s…

Java面试八股之消息队列中推模式和拉模式分别有哪些使用场景

消息队列中推模式和拉模式分别有哪些使用场景 消息队列的推模式&#xff08;Push&#xff09;和拉模式&#xff08;Pull&#xff09;各有不同的使用场景和优缺点。下面我会详细介绍这两种模式及其适用场景&#xff1a; 推模式&#xff08;Push&#xff09; 特点&#xff1a;…

进哥在线shinyapps工具(自备)

Jingle’s shinyapps toolkit – 王进的个人网站 (jingege.wang) 一些常用生物信息学分析可视化apps&#xff0c;以及一些分子生物学分析工具&#xff08;尴尬&#xff0c;一直忘记附上链接&#xff09; Jingles toolkit 可以进行多种分析&#xff1a; 1. General plots示例…

putty中修改默认窗口大小和字体、字号

在WinSCP中调用putty&#xff0c;发现默认窗口太小&#xff0c;字号也很小&#xff0c;非常不友好。现在显示器都是1080p起步&#xff0c;所以很有必要修改之。 以中文版v0.70为例&#xff0c;方法&#xff1a; 1. 点击左上角图标 &#xff0c;选择下拉菜单中的“修改设置”&…

Qt-信号和槽(8)

目录 信号的概念 Qt中的信号三要素 connect函数 connect的原型 connect的使用 信号函数和槽函数 参数匹配 close关闭槽函数 运行结果 第一个问题&#xff1a;怎么知道 手册使用 第二个问题&#xff0c;为什么可以直接传递函数指针 自定义槽函数 第一种自定义槽函…

Flask详细教程

1、Flask是什么&#xff1f; Flask是一个非常小的PythonWeb框架&#xff0c;被称为微型框架&#xff08;类似Java的SpringBoot&#xff09;&#xff1b;只提供了一个稳健的核心&#xff0c;其他功能全部是通过扩展实现的&#xff1b;意思就是我们可以根据项目的需要量身定制&a…

前端css动画transform多个属性值写法

X轴平移400px transform: translateX(400px); X轴平移400px并缩小0.5倍 transform: translateX(400px) scale(0.5); X轴平移400px并旋转45度 transform: translateX(400px) rotate(45d…

农产品智慧物流系统pf

TOC springboot537农产品智慧物流系统pf 第1章 绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。所以各行…

全新分支版本!微软推出Windows 11 Canary Build 27686版

已经很久没有看到 Windows 11 全新的分支版本了&#xff0c;今天微软发布 Windows 11 Canary 新版本&#xff0c;此次版本号已经转移到 Build 27xxx&#xff0c;首发版本为 Build 27686 版。 此次更新带来了多项改进&#xff0c;包括 Windows Sandbox 沙盒功能切换到 Microsof…

『 Linux 』利用UDP套接字实现简单群聊

文章目录 服务端通过传入命令处理实现远程命令执行使用Windows编辑UDP客户端实现Windows远程控制Linux接收套接字的其他信息UDP套接字简单群聊服务端UDP套接字简单群聊客户端运行测试及分离输入输出 参考代码 服务端通过传入命令处理实现远程命令执行 『 Linux 』利用UDP套接字…

led台灯对眼睛好不好?台灯太亮会影响视力吗?解锁护眼台灯小知识

中国的近视情况十分严峻&#xff0c;尤其在青少年群体中表现得更为突出。据统计&#xff0c;中国青少年的近视率高居世界第一&#xff0c;这主要与长时间的近距离用眼、过度使用电子产品以及户外活动时间减少等因素有关。优质的护眼台灯不仅能减少近视的发生率&#xff0c;还能…

创建一个简单的Vue3.0应用程序

1、Vue.createApp() 方法的介绍 每一个 Vue.js 的应用都需要创建一个应用程序的实例对象并挂载到指定 DOM 上。在 Vue3.0 中&#xff0c;创建一个应用程序实例的语法格式如下&#xff1a; Vue.createApp(App) createApp() 是一个全局 API&#xff0c;它接收一个根组件选项对…

基于SpringBoot+Vue疫情物资捐赠和分配系统--论文pf

TOC springboot518基于SpringBootVue疫情物资捐赠和分配系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往…

开源一款H5自适应留言表白墙php源码下载

开源一款H5自适应留言表白墙php源码下载&#xff0c;优点就是安装简单&#xff0c;功能实用[滑稽][滑稽] 缺点就是UI简陋&#xff0c;功能稀少 第一张是首页&#xff0c;第二张是查看留言 第三张是留言列表(10秒自动刷新)&#xff0c;第四张是表白墙界面