聊聊Netty中几个重要的生命周期

news2024/11/23 15:51:22

写在文章开头

Netty内置了各种开箱即用的处理器,把握好处理器中几个比较重要的生命周期回调用助于我们编写出强大的网络通信程序,所以本文将基于一个简单的示例和源码介绍一下Netty中几个比较重要的生命周期函数,希望对你有帮助。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解Netty几个核心的生命周期

基于基础服务端程序演示各个生命周期回调

首先我们给出服务端的处理器handler代码示例,可以看到笔者重写了每一个生命周期函数,读者可以参考输出了解每一个回调的用意:

public class LifeCyCleHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

        System.out.println("逻辑处理器被添加调用handlerAdded");
        super.handlerAdded(ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel注册到eventLoop上,执行channelRegistered");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接准备就绪,调用channelActive");
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("此时有数据可读,执行channelRead");
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("数据阅读完成,调用channelReadComplete");
        super.channelReadComplete(ctx);
    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel被关闭,调用channelInactive");
        super.channelInactive(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("NIO线程解绑该channel,调用channelUnregistered");
        super.channelUnregistered(ctx);
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("执行handlerRemoved,逻辑处理器被移除");
        super.handlerRemoved(ctx);
    }
}

对应的服务端代码如下所示,逻辑比较简单,收到连接并将该socket封装为channel之后,为这个channel分配一个LifeCyCleHandler处理器:

public static void main(String[] args) {

        // 引导类负责引导服务端启动工作
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        // 以下两个对象可以看做是两个线程组

        // 负责监听端口,接受新的连接
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 负责处理每一个连接读写的线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());

        // 配置线程组并指定NIO模型
        serverBootstrap.group(bossGroup, workerGroup)
                //设置IO模型,这里为NioServerSocketChannel,建议Linux服务器使用 EpollServerSocketChannel
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        //为每一个新建的连接添加LifeCyCleHandler
                        nioSocketChannel.pipeline().addLast(new LifeCyCleHandler());

                    }
                });

        //绑定端口
        serverBootstrap.bind(9191);
    }

此时我们将服务端程序启动,通过cmdtelnet指令与其建立连接之后,我们可以看到这样的输出:

逻辑处理器被添加调用handlerAdded
channel注册到eventLoop上,执行channelRegistered
连接准备就绪,调用channelActive

由此可知服务端检测到新连接并完成channel分配之后,每当添加一个handler就会回调handlerAdded方法,然后channel会被注册到处理channel读写事件的eventLoop上,此时就会回调channelRegistered方法,自此连接建立状态属于正式激活,由此触发channelActive回调:

在这里插入图片描述

然后通过命令行发送数据,又会收到下面这样一段输出:

此时有数据可读,执行channelRead
数据阅读完成,调用channelReadComplete

由此可知收到可读数据时,Netty会触发channelRead回调,一旦数据读取完成就会触发channelReadComplete回调:

在这里插入图片描述

我们将命令行界面关闭,又看到下面这样一段输出:

channel被关闭,调用channelInactive
NIO线程解绑该channel,调用channelUnregistered
执行handlerRemoved,逻辑处理器被移除

由此可知,一旦断开连接,Netty服务端会执行如下步骤:

  1. channel对应socket连接关闭,触发channelInactive回调,此时TCP层面已经不再是establish状态了。
  2. channel从处理客户端channeleventLoop线程中移除,触发channelUnregistered回调
  3. channel的处理器移除触发handlerRemoved回调。

在这里插入图片描述

详解各个生命周期回调

Netty服务端收到客户端连接时,将当前socket封装为AbstractChannel之后,调用AbstractChannelregister0调用 pipelineinvokeHandlerAddedIfNeeded触发handlerAdded回调。

private void register0(ChannelPromise promise) {
          
           		//......
           		//回调当前channel的所有handler的handlerAdd
                pipeline.invokeHandlerAddedIfNeeded();

			//......
}

于是就来到我们上文所配置的ChannelInitializerhandlerAdded此时就会来到initChannel将我们的生命周期处理器添加进去

@Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
          	//由此调用到我们引导类配置的添加handler的逻辑nioSocketChannel.pipeline().addLast(new LifeCyCleHandler());
            initChannel(ctx);
        }
    }

完成处理器添加之后通过callHandlerAdded0方法触发handlerAdded回调:

@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
			//添加handler
            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

          //......
			//查看当前添加处理器的线程是否是eventLoop线程,如果是则直接调用callHandlerAdded0触发handlerAdded,反之提交任务通过callHandlerAdded0执行handlerAdded
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

随后channel会将自己的事件注册到eventLoop上,本质上调用AbstractChannelregister0触发fireChannelRegistered触发回调,后续步骤完成后触发fireChannelActive回调:

private void register0(ChannelPromise promise) {
            try {
            	//......
            	//注册到eventLoop上
                doRegister();
                neverRegistered = false;
                //......
                //调用fireChannelRegistered触发channelRegistered回调
                pipeline.fireChannelRegistered();
              	//如果channel准备就绪则调用fireChannelActive触发channelActive回调
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } 
                    //......
                }
            } catch (Throwable t) {
                //......
            }
        }

后续读事件将消息传播到AbstractNioByteChannelread方法的其内部通过fireChannelRead触发channelRead回调后,调用fireChannelReadComplete触发channelReadComplete回调:

	 @Override
        public final void read() {
           //......
            try {
                do {
                    byteBuf = allocHandle.allocate(allocator);
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                     //......
                     //触发channelRead
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
				//读取完成后触发channelReadComplete
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

	//.....
}

当客户端channel连接断开,eventLoop轮询到这个事件后触发当前channel关闭处理,便执行AbstractChanneldoDeregister方法将其从eventLoop中移除,然后在finally语句块中执行channelInactivechannelUnregistered回调:

private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
            //......
            invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                    //将channel从eventLoop中移除
                        doDeregister();
                    } catch (Throwable t) {
                        logger.warn("Unexpected exception occurred while deregistering a channel.", t);
                    } finally {
                    //执行channelInactive和channelUnregistered回调
                      if (fireChannelInactive) {
                            pipeline.fireChannelInactive();
                        }
                       
                        if (registered) {
                            registered = false;
                            pipeline.fireChannelUnregistered();
                        }
                }
            });
        }

可能会有读者文handlerRemoved在哪里执行,实际上在channelUnregistered内部完成channelUnregistered回调后就会调用destroy方法,触发handlerRemoved回调:

@Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        	//触发channelUnregistered回调
            ctx.fireChannelUnregistered();

           //destroy内部执行handlerRemoved
            if (!channel.isOpen()) {
                destroy();
            }
        }

handlerRemoved回调的逻辑我们步入destroy内部的destroyDown就可以看到这段逻辑:

private void destroyDown(Thread currentThread, AbstractChannelHandlerContext ctx, boolean inEventLoop) {
     		//......
            if (inEventLoop || executor.inEventLoop(currentThread)) {
                synchronized (this) {
                    remove0(ctx);
                }
                //触发handlerRemoved回调
                callHandlerRemoved0(ctx);
            }
            //......
}

各个周期函数使用建议

所以简单小结一下:

  1. handlerAddedhandlerRemoved更适合连接初始化时资源的申请和释放。
  2. channelReadComplete处于读取完数据之后的回调,服务端读取数据时一般会做一些回复,这时候我们常常会用到writeAndFlush,实际上如果我们希望提升程序性能,更建议使用write方法然后在channelReadComplete执行ctx.channel().flush()进行批量刷新更由此保证程序执行性能。
  3. channelActivechannelInactive在含义在可以直接理解为TCP连接的建立与释放,所以更适用于统计连接数。

对此我们也给出《Netty in action》中的对于生命周期的总结,读者可以自行阅读回顾一下:

在这里插入图片描述

然后是channelHandler的生命周期:

在这里插入图片描述

ChannelInboundHandler的生命周期:

在这里插入图片描述

最后就是ChannelOutboundHandlerd的生命周期:

在这里插入图片描述

小结

自此笔者从使用和源码示例上将Netty中的几个核心生命周期分析完成,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

《跟着闪电侠学Netty》

《Netty in action》

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

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

相关文章

九州未来参编,《边缘计算产业发展研究报告》正式发布

日前&#xff0c;由中国通信标准化协会主办的第四届“云边协同”大会暨首届分布式算力论坛在北京成功举办。大会聚焦云边端分布式算力领域技术新突破、应用新场景以及发展新价值&#xff0c;搭建政产学研用交流对接平台&#xff0c;深化产业链协同开放合作。 会上&#xff0c;由…

Cookie-Monster:一款针对Web浏览器的安全分析与数据提取工具

关于Cookie-Monster Cookie-Monster是一款针对常见Web浏览器的安全分析与数据提取工具&#xff0c;该工具可以帮助广大研究人员提取并分析Edge、Chrome和Firefox浏览器中的Cookie数据。 Cookie-Monster适用于红队和蓝队成员&#xff0c;能够提取WebKit主密钥&#xff0c;找到具…

无监督学习与强化学习基础

就是训练数据无标签&#xff0c;算法自动对数据进行分类&#xff0c;听着很神奇&#xff0c;但学了机器学习以后&#xff0c;除了神经网络比较悬&#xff0c;对人像是个黑盒&#xff0c;别的都是基于数学的分类算法&#xff0c;无监督学习也不例外。 聚类—K-means算法 坐标轴…

Postman下载安装~用于springboot控制层测试

第一步&#xff1a;下载安装 方法1&#xff1a;在线下载 Postman API Platform 方法2&#xff1a;百度网盘 通过百度网盘分享的文件&#xff1a;Postman-win64-Setup 链接&#xff1a;https://pan.baidu.com/s/16nNfKvuNfM8z4kP1Ad-K2Q?pwdotxe 提取码&#xff1a;otxe -…

见证中国数据库的崛起:从追赶到引领的壮丽征程《三》

见证中国数据库的崛起&#xff1a;从追赶到引领的壮丽征程《三》 三、深度思考&#xff1a;中国数据库发展的经验与启示产学研用结合的创新模式应用驱动的创新路径人才培养的关键作用 【纪录片】中国数据库前世今生 在数字化潮流席卷全球的今天&#xff0c;数据库作为IT技术领域…

Java高级流

高级流 java将IO分为了两类 节点流:又称为"低级流" 特点:直接链接程序与另一端的"管道"&#xff0c;是真实读写数据的流IO一定是建立在节点流的基础上进行的。文件流就是典型的节点流(低级流) 处理流:又称为"高级流" 特点:不能独立存在&#x…

开源项目的发展趋势,以及参与开源项目可以获得的经验和成果,以及涉及到的注意事项

目录 一、当前开源项目的发展趋势 1. 全球化协作与社区增长 2. 多领域技术创新与迭代加速 3. 开放协作模式 4. 商业化与产业融合 5. 安全性与隐私保护 6. 跨界融合与生态构建 7. 政策支持 二、参与开源项目的经验和收获 1. 技术能力提升 2. 团队协作与沟通能力 3.领…

阿里微服务质量保障系列:异步通信模式以及测试分析

软件质量保障 所寫即所思|一个阿里质量人对测试的所感所悟。 1. 异步通信模式 最常见的方式就是异步消息通信。使用消息机制时,服务之间的通信采用异步交换消息的方式完成。基于消息机制的应 用程序通常使用消息代理,它充当服务之间的中介。另一种选择是使用无代理架构,通…

实验室自动测试系统产品化注意问题

在实验室开发的自动测试系统转向产品化时&#xff0c;需要综合考虑多个方面&#xff0c;以确保系统的稳定性、可靠性和可维护性。以下是一些关键问题和建议&#xff1a; 1. 硬件选择与兼容性 在实验室中&#xff0c;可能会使用一些专业的实验设备或定制的硬件&#xff0c;但在…

一文带你掌握C++模版

12. C模板 什么是模板 模板编程也可以叫做泛型编程&#xff0c;忽略数据类型的一种编程方式 //求最值问题 int Max(int a,int b) {return a>b?a:b; } double Max(int a,int b) {return a>b?a:b; } string Max(string a,string b) {return a>b?a:b; …

搭建Nginx正向代理服务器,轻松实现外部网络请求的转发

​ 本文将介绍如何使用Nginx搭建一个简单的正向代理服务器&#xff0c;实现外部网络请求的转发。通过设置正向代理&#xff0c;我们可以隐藏真实的服务器地址&#xff0c;提高访问速度&#xff0c;以及增强网络安全性 一、Nginx正向代理简介 正向代理&#xff08;Forward Pro…

基于node的学生公寓管理系统-计算机毕设 附源码 06412

基于node的学生公寓管理系统 摘 要 本论文主要论述了如何使用Node.js框架和Express框架开发一个基于Node的学生公寓管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S结构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0…

无人机环保行业解决方案-应急环境污染处理

无人机环境应急处理 传统环境应急的典型挑战 发生环境应急事件时&#xff0c;最重要的是快速获取前方信息。然而&#xff0c;有毒气体 和易燃易爆品多&#xff0c;存在二次爆炸风险&#xff0c;严重威胁人身安全。无人机可快 速赶到事故现场&#xff0c;查看周边环境、污染物…

免费开源的搜索工具,支持搜索文件内容,绿色免安装

dnGrep是一款功能强大的开源Windows搜索工具&#xff0c;旨在帮助用户高效地在各种文档和压缩文件中查找文本内容。它支持多种查询方式&#xff0c;包括文本、正则表达式&#xff08;Regex&#xff09;、XPath以及音序查询。 dnGrep主要功能&#xff1a; 多格式支持&#xff…

网络安全学习平台top10_网络安全训练平台

前言 1.Hack In The Box&#xff1a;http://www.hackinthebox.org/ 2.Hellbound Hackers&#xff1a;https://www.hellboundhackers.org/ 3.Exploit Database&#xff1a;https://www.exploit-database.net/ 4.Hacking-Tutorial&#xff1a;https://www.hacking-tutorial.c…

【Python实战】如何优雅地实现 PDF 去水印?

话接上篇&#xff0c;自动化处理 PDF 文档&#xff0c;完美实现 WPS 会员功能 小伙伴们更关心的是如何去除 PDF 中的水印~ 今天&#xff0c;就来分享一个超简单的 PDF 去水印方法~ 1. 原理介绍 在上一篇中&#xff0c;我们介绍了如何将 PDF 文档转换成图片&#xff0c;图片…

前端必知必会-html中input的type设置

文章目录 HTML type的设置输入类型文本输入类型密码输入类型提交输入类型重置输入类型单选按钮输入类型复选框输入类型按钮输入类型颜色输入类型日期输入类型 Datetime-local输入类型电子邮件输入类型图像输入类型 文件输入类型 隐藏输入类型 月份输入类型 数字输入类型范围输入…

更换收银系统时如何迁移会员数据

系统介绍 专门为零售行业的连锁店量身打造的收银系统&#xff0c;适用于常规超市、生鲜超市、水果店、便利店、零食专卖店、服装店、母婴用品、农贸市场等类型的门店使用。同时线上线下数据打通&#xff0c;线下收银的数据与小程序私域商城中的数据完全同步&#xff0c;如商品…

js 实现数组转树形数据(2024-08-01)

要将数组转换为树形结构&#xff0c;通常需要一个数组&#xff0c;其中每个元素都包含一个父节点的引用。以下是一个使用JavaScript实现的函数&#xff0c;假设每个元素都有一个唯一的 【id 】和一个指向其父元素的【pId】 /*** 数组转树形结构* param {array} list 被转换的数…

【编程刷级之路】大学新生的最佳入门攻略

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 目录 引言 方向一&#xff1a;编程语言选择 方向二&#xff1a;学习资源推荐 方向三&#xff1a;学…