【图解IO与Netty系列】Netty源码解析——事件循环

Netty源码解析——事件循环

  • Netty事件循环
  • 源码解析
    • select()
    • processSelectedKeys()
      • NioMessageUnsafe#read()
      • NioByteUnsafe#read()
    • runAllTasks()

Netty事件循环

当Netty服务端启动起来以后,就可以接受客户端发送的请求,接收到客户端发来的请求后就会有事件就绪,有事件就绪就会在Netty的事件循环中被监听到并处理,我们下面看看Netty事件循环的逻辑。

在这里插入图片描述

Netty中的NIO事件循环的代码位于NioEventLoop的run()方法内部,这个方法总体是一个for(;;)死循环,然后for循环里头依次执行的三个重要方法分别是:

  1. select():调用Selector#select()方法监听注册到Selector上的Channel,等待Channel关注的事件就绪。
  2. processSelectedKeys():处理就绪事件,会调用ChannelPipeline处理,ChannelPipeline又会通过责任链模式调用里面的ChannelHandler处理。
  3. runAllTasks():处理NioEventLoop中的taskQueue的异步任务。

这就是Netty事件循环的大体逻辑,下面我们进入代码解析。

源码解析

select()

    private int select(long deadlineNanos) throws IOException {
        if (deadlineNanos == NONE) {
            return selector.select();
        }
        long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
        return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
    }

在NioEventLoop的事件循环中,首先是select()方法的调用,select()方法里面就是调用NioEventLoop对应的Selector的select()方法,阻塞当前线程,监听注册到Selector的Channel,等待事件就绪。

在这里插入图片描述

当有事件就绪时,当前线程就会解阻塞,然后调用processSelectedKeys()方法处理就绪事件。

processSelectedKeys()

NioEventLoop的 processSelectedKeys()方法会进入processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法。

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        try {
            int readyOps = k.readyOps();
            ...
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (...) {...}
    }

无论是accept事件,还是read事件时,都是调用unsafe.read()方法。

在这里插入图片描述

只是这里的Unsafe的类型有可能不一样,如果是NioServerSocketChannel的话,那么Unsafe的类型就是NioMessageUnsafe,是在创建NioServerSocketChannel时就创建好的,我们看一下NioMessageUnsafe的read()方法。

NioMessageUnsafe#read()

    private final class NioMessageUnsafe extends AbstractNioUnsafe {

        private final List<Object> readBuf = new ArrayList<Object>();

        @Override
        public void read() {
            	...
                doReadMessages(readBuf);
				...
                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    ...
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                ...
        }
    }

重要方法就两个doReadMessages(readBuf)和pipeline.fireChannelRead(readBuf.get(i)),其他代码全部省略不看。

在这里插入图片描述

我们先看doReadMessages(readBuf)

@Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());
		...
                buf.add(new NioSocketChannel(this, ch));
				...
    }

SocketUtils.accept(javaChannel())里面是调用ServerSocketChannel的accept()方法,返回一个SocketChannel。

然后new NioSocketChannel(this, ch)把返回的SocketChannel包装成NioSocketChannel,放入buf中,这个buf就是外面read()方法的readBuf。

在这里插入图片描述

NioSocketChannel的构造方法调用父类AbstractNioByteChannel的构造方法:

    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

可以看到指定关注的事件类型是read事件,再看看AbstractNioByteChannel的父类AbstractNioChannel的构造方法

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (...) {...}
    }

继续调用父类的构造方法,然后保存了NioSocketChannel和关注的事件类型OP_READ,设置NioSocketChannel为非阻塞。

再看AbstractNioChannel的父类AbstractChannel的构造方法

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

创建一个Unsafe,然后初始化了ChannelPipeline。newUnsafe()方法会进入NioSocketChannel的newUnsafe()方法,创建的时NioSocketChannelUnsafe类型的Unsafe,因此NioSocketChannel的Unsafe类型就是NioSocketChannelUnsafe。

    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();
    }

NioSocketChannel的构造方法总结起来就是做了5件事:

  1. 创建并保存NioSocketChannelUnsafe
  2. 创建并保存ChannelPipeline
  3. 保存SocketChannel
  4. 保存关注的事件类型OP_READ
  5. 设置SocketChannel为非阻塞

在这里插入图片描述

然后回到unsafe.read()方法,接下来执行的代码pipeline.fireChannelRead(readBuf.get(i))就是调用触发就绪事件的Channel对应的ChannelPipeline的fireChannelRead(…)方法,触发ChannelPipeline中每个处理入站事件的ChannelHandler的入站事件处理。

ChannelPipeline的fireChannelRead(…)方法会从头到尾以责任链的处理方式调用每个ChannelInboundHandler类型的channelRead(…)方法。NioServerSocketChannel的ChannelPipeline中的ChannelHandler是ServerBootstrapAcceptor。我们看看ServerBootstrapAcceptor的channelRead(…)方法。

        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            try {
                childGroup.register(child).addListener(new ChannelFutureListener() {...});
            } catch (...) {...}
        }

这里的参数msg的类型是NioSocketChannel,就是上面放入buf中的NioSocketChannel,调用pipeline.fireChannelRead(readBuf.get(i))方法时readBuf.get(i)就是中buf中取出NioSocketChannel作为参数传进来。

然后child.pipeline().addLast(childHandler)这一行这里的childHandler是我们定义的ChannelInitializer,这里放入NioSocketChannel的ChannelPipeline中,当NioSocketChannel里面的SocketChannel被注册到Selector之后,会触发ChannelInitializer的调用,初始化ChannelPipeline。

然后childGroup.register(child)就是把这个NioSocketChannel注册到workerGroup中的其中一个NioEventLoop上,也就是把NioSocketChannel中的SocketChannel注册到workerGroup中的其中一个NioEventLoop的Selector上。

在这里插入图片描述

这里NioSocketChannel注册的细节跟NioServerSocketChannel的注册是一样的,上一篇文章已经分析过,这里就不重复了。

NioSocketChannel注册好之后,就可以接收客户端发来的数据,于是又有read事件触发,此时调用的unsafe.read(),就是NioSocketChannelUnsafe的父类NioByteUnsafe的read()方法了。

NioByteUnsafe#read()

        @Override
        public final void read() {
            		...
                    byteBuf = allocHandle.allocate(allocator);
                    doReadBytes(byteBuf);
					...
                    pipeline.fireChannelRead(byteBuf);
					...
    	}

就是通过allocator分配一个ByteBuf,然后把Channel中的数据读取到ByteBuf中,然后调用pipeline.fireChannelRead(byteBuf)触发ChannelPipeline的处理,然后ChannelPipeline中的ChannelHandler就会处理byteBuf中的数据,这里的ChannelPipeline中的ChannelHandler就是我们定义的ChannelInitializer组装到ChannelPipeline中的ChannelHandler了。

在这里插入图片描述

runAllTasks()

runAlllTasks方法其实就是从NioEventLoop的taskQueue中不停的取出task并执行。

    protected boolean runAllTasks(long timeoutNanos) {
        ...
        Runnable task = pollTask();
        ...
        for (;;) {
            safeExecute(task);
			...
            task = pollTask();
            if (task == null) {
                ...
                break;
            }
        }
		...
    }

可以看到,就是在一个for循环中,pollTask()方法取出task,然后在下一轮循环中调用safeExecute(task)去执行,safeExecute(task)里面就是调用task.run()方法直接执行,没什么好看的。如果pollTask()方法取出的task为null,那么就break结束循环。

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

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

相关文章

Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流

技术背景 VR头显需要更高的分辨率以提供更清晰的视觉体验、满足沉浸感的要求、适应透镜放大效应以及适应更广泛的可视角度&#xff0c;超高分辨率的优势如下&#xff1a; 提供更清晰的视觉体验&#xff1a;VR头显的分辨率直接决定了用户所看到的图像的清晰度。更高的分辨率意…

微服务改造启动多个 SpringBoot 的陷阱与解决方案

在系统运行了一段时间后&#xff0c;业务量上升后&#xff0c;生产上发现java应用内存占用过高&#xff0c;服务器总共64G&#xff0c;发现每个SpringBoot占用近12G的内存&#xff0c;我们项目采用微服务架构&#xff0c;有多个springboot应用。 一下子内存就不够用了&#xf…

基于SpringBoot+Vue在线考试报名系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

<Rust><iced>基于rust使用iced构建GUI实例:如何将svg格式转为ico格式图片?

前言 本专栏是Rust实例应用。 环境配置 平台&#xff1a;windows 软件&#xff1a;vscode 语言&#xff1a;rust 库&#xff1a;iced、iced_aw 概述 本文是专栏第4篇实例&#xff0c;依旧是一个图像格式转换程序&#xff0c;基于rust的svg库resvg、图像处理库image以及文件处…

Python xlwt库:写excel表格

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

电路仿真实战设计教程--平均电流控制原理与仿真实战教程

1.平均电流控制原理: 平均电流控制的方块图如下,其由外电路电压误差放大器作电压调整器产生电感电流命令信号,再利用电感电流与电流信号的误差经过一个电流误差放大器产生PWM所需的控制电压,最后由控制电压与三角波比较生成开关管的驱动信号。 2.电流环设计: 根据状态平…

C语言:生命周期和作用域,static和extern

关键字static与extern 1.作用域&#xff08;scope&#xff09;&#xff1a;代码中能够访问到变量的范围&#xff08;变量可以被使用的文本区间&#xff09;。&#xff08;分为全局作用域和局部作用域&#xff09; ☺全局作用域&#xff1a;在整个程序中都能访问的变量。通常…

【数据结构】第十九弹---C语言实现冒泡排序算法

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、冒泡排序基本思想 2、代码的初步实现 3、代码的优化 4、代码的测试 5、时空复杂度分析 6、模拟实现qsort 6.1、冒泡排序函数 6.2、交换数…

【PyTorch】【机器学习】图片张量、通道分解合成和裁剪

一、导入所需库 from PIL import Image import torch import numpy as np import matplotlib.pyplot as plt二、读取图片 pic np.array(Image.open(venice-boat.jpg))上述代码解释&#xff1a;先用Image.open()方法读取jpg格式图片&#xff0c;再用np.array()方法将图片转成…

STM32单片机-BKP和RTC

STM32单片机-BKP和RTC 一、Unix时间戳1.1 时间戳转换 二、BKP(备份寄存器)三、RTC(实时时钟)3.1 RTC工作原理 四、代码部分4.1 BKP备份寄存器4.2 RTC实时时钟 一、Unix时间戳 Unix时间戳定义为从伦敦时间的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒时间戳…

Django集成OpenAI

Django集成OpenAI 通过前面 django 框架的基本开发知识&#xff0c;我们现在可以开始在 django 上做稍微深一点当然应用开发了。 这一章开始编写怎么集成调用 openai &#xff0c;设置环境以及 openai 的基础知识。 大家都知道 ai 的多模态逐渐扩大&#xff0c;各种应用层出…

【LeetCode:2663. 字典序最小的美丽字符串 + 贪心】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

性能工具之 JMeter 常用组件介绍(五)

文章目录 一、Jmeter中参数取值1、Test Plan中添加变量2、User Defined Variables 二、Jmeter中CSV Data Set Config三、Timer:定时器4、Gaussian Random Timer 高斯随机定时器5、JSR223 Timer JSR223定时器6、Poisson Random Timer 泊松随机定时器7、Synchronizing Timer 同步…

复分析——第4章——Fourier变换(E.M. Stein R. Shakarchi)

第4章 Fouier变换 Raymond Edward Alan Christopher Paley, Fellow of Trinity College, Cambridge, and International Research Fellow at the Massachusetts Institute of Technology and at Harvard University, was killed by an avalanche on April 7, 1933, whi…

记某模版菠菜管理后台登录思路

1.前言 由于小程序的便捷性&#xff0c;越来越多的应用迁移到了了小程序上&#xff0c;由此伴随着小程序上线前的日常渗透测试工作也开始增加。但小程序的测试中经常会遇到数据包被加密了&#xff0c;导致无法进行改包测试。和测试网页数据包加密一样&#xff0c;就需要找到小…

Stable Diffusion 3 文本生成图像 在线体验 原理分析

前言 本文分享使用Stable Diffusion 3实现文本生成图像&#xff0c;可以通过在线网页中免费使用的&#xff0c;也有API等方式访问。 同时结合论文和开源代码进行分析&#xff0c;理解其原理。 Stable Diffusion 3是Stability AI开发的最新、最先进的文本生成图像模型&#x…

Linux中部署MySQL环境方法(仓库安装)

1.进入MySQL官网 2.进入MySQL社区版下载 3.使用yum方式下载MySQL 4.使找到对应系统的对应包的链接 复制 5.linux命令行中使用命令通过对应链接下载该软件包 rpm -i https://repo.mysql.com//mysql80-community-release-el9-1.noarch.rpm 警告&#xff1a;/var/tmp/rpm-tmp.so…

45、基于深度学习的螃蟹性别分类(matlab)

1、基于深度学习的螃蟹性别分类原理及流程 基于深度学习的螃蟹性别分类原理是利用深度学习模型对螃蟹的图像进行训练和识别&#xff0c;从而实现对螃蟹性别的自动分类。整个流程可以分为数据准备、模型构建、模型训练和性别分类四个步骤。 数据准备&#xff1a; 首先需要收集包…

分享一个 Fail2ban 过滤规则

今天明月给大家分享个 Fail2ban 的过滤&#xff08;Filter&#xff09;规则&#xff0c;有关 Fail2ban 的文章大家可以参考【服务器全面使用 Fail2Ban 初见成效】和【使用 Fail2ban 禁止垃圾采集爬虫&#xff0c;保护 Nginx 服务器】等文了解&#xff0c;总之 Fail2ban 是 Linu…

如何跳出认知偏差,个人认知能力升级

一、教程描述 什么是认知力&#xff1f;认知力&#xff08;cognitive ability&#xff09;&#xff0c;实际上就是指一个人的认知能力&#xff0c;是指人的大脑加工、储存和提取信息的能力&#xff0c;或者主观对非主观的事物的反映能力&#xff0c;如果变成大白话&#xff0c…