netty组件详解-上

netty服务端示例:

private void doStart() throws InterruptedException {
        System.out.println("netty服务已启动");
        // 线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建服务器端引导类
            ServerBootstrap server = new ServerBootstrap();
            // 初始化服务器配置
            server.group(group) // 配置处理客户端的连接线程组
                    .channel(NioServerSocketChannel.class) // 指定channel为 NioServerSocketChannel
                    .localAddress(port) // 配置服务端口号
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 指定客户端通信的处理类,添加到pipline中,进行初始化
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            // 绑定端口,sync()会阻塞到完成
            ChannelFuture sync = server.bind().sync();
            // 阻塞当前线程,直到服务器的ServerChannel被关闭
            sync.channel().closeFuture().sync();
        }finally {
            // 关闭资源
            group.shutdownGracefully().sync();
        }
    }

netty各组件解析;

  1. EventLoop 与 EventGroup
    EventLoop : 单线程+任务队列
    EventGroup: 多个EventLoop
    在这里插入图片描述
    思考问题: netty底层每个channel中的事件都是由同一个EventLoop来处理的,而EventLoop是单线程的,这样无需考虑为了并发冲突而加锁的问题,提升了性能。并发高效的本质,不是关注如何科学安全的加锁,而是想尽办法避免加锁,来提升性能。
  2. channel接口
    每个channel都会被注册到一个EventLoop上,以下是Channel抽象出的方法
    在这里插入图片描述
    每个channel都有自己的生命周期,channel在生命周期的不同节点会回调不同的处理函数:
    (1)当channel被注册到EventLoop中时,会调用isRegistered()方法,来确认是否注册成功
    (2)每个channel在处理事件时,都有一个对应的pipeline,这个pipeline中以责任链模式来处理事件
  3. channelPipeline &&channelHandler
    channelPipeline 的实现是一个双向链表,链表的每个节点对应一个channelHandler,每个channel在处理事件时会调用channelPipeline中的各个channelHandler
    channelHandler也有自己的生命周期,在添加到channelPipeline 或被移除出channelPipeline 时,会调用相应的生命周期方法。
    在这里插入图片描述
  4. channelPipeline的入站事件和出站事件
    netty如何在同一个channelPipeline中区分出出站事件链路和入站事件链路?
    在这里插入图片描述
    在这里插入图片描述
  5. ChanelHandlerContext 上下文
    表示ChannelPipeline和ChannelHandler关联
    ChannelPipeline 是双向链表
    ChanelHandlerContext 维护了双向链表的pre和 next 指针
    具体实现:
    在这里插入图片描述
    ChanelHandlerContext 的作用不仅仅只是维护了指针信息,而且还需要控制channelPipeline中每个ChannelHandler处理的方向和数据流动,比如像下面这些:
    在这里插入图片描述
    ChanelHandlerContext 中的写方法区别:
    在这里插入图片描述
    pipline中有一系列链式的处理逻辑:
    在这里插入图片描述
    ctx.write(in) / ctx.writeAndFlush(in):
    在某个入站事件中的handler中直接找到pipline中最近的出站事件节点,在出站事件中输出数据.
    ctx.pipeline().write(in) /ctx.channel().write(in)
    在当前入站事件handler结束后,继续按照pipline中handler的顺序依次处理后,在输出数据。
    对比上面两种做法:
    可根据业务需求进行优化,不经过pipeline直接返回的效率更快。
  6. channelHandler的适配器
    channelHandler根据功能,设计了几种适配器,其中包括ChannelOutboundHandlerAdapter出站事件适配器
    问题:为什么ChannelOutboundHandlerAdapter中包含一个read()事件方法
    netty将read()动作打包成一个读事件放到了pipeLine中
  7. channelHandler的并发共享机制:
    根据netty的设计,每个socketchannel都是由一个EventLoop线程处理的,每个channel中包含一个pipeline,而pipeline中的每个handler在使用的时候都是重新new 的一个实例,由于每个socket都是独立的线程隔离的,因此每个socket是线程安全的。
    但有些业务场景需要各个socket之间共享通信,比如要统计服务器接收到的报文总数。此时就要维护一个共享变量 total,每来一个新的
    socket都要去进行 total+1的操作,此时就会产生并发安全问题。
    netty如何解决这个问题呢,我们可以定义一个共享的hander,这个hander的定义成一个全局共享的,每个socketchannel的pipeline都添加这个handler,通过这个并发共享的handler来实现socket进程间的通信,代码如下:
    (1)首先定义一个共享的handler,其内部实现统计报文的业务逻辑:
// 这个注解的含义就是声明这个handler是共享的
// 如果不声明这个注解的话,在添加到pipline中时会报错
@ChannelHandler.Sharable
public class MessageCountHandler extends ChannelDuplexHandler {
    private static final Logger LOG = LoggerFactory.getLogger(MessageCountHandler.class);
	
    private AtomicLong inCount = new AtomicLong(0);
    private AtomicLong outCount = new AtomicLong(0);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        LOG.info("收到报文总数:"+inCount.incrementAndGet());
        super.channelRead(ctx, msg);
    }

    @Override
    public void flush(ChannelHandlerContext ctx) throws Exception {
        LOG.info("发出报文总数:"+outCount.incrementAndGet());
        super.flush(ctx);
    }
}

(2)服务器端实现

public void start() throws InterruptedException {
		// 将统计报文的handler定义成共享变量
        final MessageCountHandler messageCountHandler = new MessageCountHandler();
        /*线程组*/
        EventLoopGroup boss  = new NioEventLoopGroup();
        EventLoopGroup work  = new NioEventLoopGroup();
        try {
            /*服务端启动类*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(boss,work)
            .channel(NioServerSocketChannel.class)/*指定使用NIO的通信模式*/
            .localAddress(new InetSocketAddress(port))/*指定监听端口*/ 
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    ch.pipeline().addLast(messageCountHandler); // 添加一个共享的hander到pipeline中
                    ch.pipeline().addLast(new EchoServerMCHandler());
                }
            });
            ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞到完成*/
            LOG.info("服务器启动完成");
            f.channel().closeFuture().sync();/*阻塞当前线程,直到服务器的ServerChannel被关闭*/
        } finally {
            boss.shutdownGracefully().sync();
            work.shutdownGracefully().sync();
        }
    }
  1. netty内存泄露与资源释放注意事项
    netty底层的实现机制是java的nio,因此其通信机制是面向缓冲区的,在nio中,当channel中有事件发生时,比如读事件时,会将数据读取到一块直接内存中,当我们处理完这部分数据的时候,应该将这块内存资源释放掉,以防止内存泄露。那么这部分netty是怎么做的呢?
    (1)netty中数据的处理是在pipeline中,由每个handler进行处理,那么netty在定义pipeline时,默认在链表的头尾帮我们各自实现了一个handler用于资源分配与释放的。
    源码:
    在这里插入图片描述
    从这个ch.pipeline()方法点进去后,找到对应实现

在这里插入图片描述
在这里插入图片描述
我们可以发现,pipeline的创建是基于DefaultChannelPipeline.class这个类
在这里插入图片描述
我们找到DefaultChannelPipeline.class这个类,可以看到这head和tail在pipeline初始化的时候就被添加进去了
在这里插入图片描述
我们找到这个方法看看这个headContext,可以看到他继承自AbstractChannelHandlerContext,并实现了入站和出站事件处理方法
在这里插入图片描述
可以看到这是一个内部类,可以看到,他是实现了资源释放及异常处理的方法
在这里插入图片描述
正常情况下,如果事件在pipeline中正常传递的情况下,我们无需手动去管理资源,但是有一种情况,需要手动释放资源
在这里插入图片描述
上面的情况就是,当事件读取发生异常,或因为某些业务需求,不能将该事件向pipeline中传递时,需要自己实现资源释放逻辑
在这里插入图片描述
此外,大部分的业务逻辑是在入站事件中资源在某个handler中读取异常时终止传递,否则就正常传递,针对这种业务,netty还单独实现了一个handler来实现异常时自动释放资源,即 SimpleChannelInboundHandler:在这里插入图片描述
实现SimpleChannelInboundHandler后,在发生异常时我们无需手动去释放资源,看源码:

在这里插入图片描述
因此,到这里,关于资源释放的问题,我们可以有三种做法:
(1)无论何时都保证让业务在pipeline中正常传递,依靠DefaultPipeLine中的head和tail来保证资源的释放
(2)在代码逻辑中手动释放资源 如: ctx.fireChannelRead(msg);
(3)继承SimpleChannelInboundHandler这个类,重写channelRead0(ChannelHandlerContext ctx, ByteBuf msg)方法

  1. 同时处理入站和出站事件
    netty中根据业务模型为我们提供了 ChannelInboundHandlerAdapter 和 ChannelOutboundHandler分别处理入站和出站事件,但是有时,我们需要同时处理入站和出站事件,这里netty为我们提供了 ChannelDuplexHandler 这个实现,我们看源码:
    在这里插入图片描述

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

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

相关文章

了解交换机接口的链路类型(access、trunk、hybrid)

上一个章节中讲到了vlan的作用及使用&#xff0c;这篇了解一下交换机接口的链路类型和什么情况下使用 vlan在数据包中是如何体现的&#xff0c;在上一篇的时候提到测试了一下&#xff0c;从PC1去访问PC4的时候&#xff0c;只从E0/0/2发送给了E0/0/3这是&#xff0c;因为两个接…

【SpringⅡ】简单高效地存储读取对象

目录 &#x1f9e5;1 配置扫描路径 &#x1f9e4;2 类注解实现 Bean 对象的存储 &#x1fa71;2.1 五大类注解的使用 &#x1f381;2.2 五大类注解之间的关系 &#x1f38f;2.3 Java 项目的标准分层 &#x1f383;3 方法注解实现 Bean 对象的存储 &#x1f388;3.1 Bean…

【论文阅读】一些多轮对话文章的体会 ACL 2023

前言 本文是对昨天看到的ACL 2023三篇多轮对话文章的分享这三个工作都是根据一些额外属性控制输出的工作&#xff0c;且评估的方面比较相似&#xff0c;可以借鉴 方法 这几篇文章都不是做general任务的&#xff0c;倾向于通过一些额外信息&#xff0c;来做specific任务 【1】…

【ceph】存储池pg个数如何设置

存储池pg个数如何设置 参考官方文档说明&#xff1a;https://old.ceph.com/pgcalc/参数说明TargePGs per OSD&#xff1a;每个OSD的pg数OSD#存储池包含osd个数%Data存储池写入数据占总OSD容量百分比Size存储池冗余数

vue2watch监听遇到的问题

1 vue 父组件里引入子组件 显示与隐藏是v-if控制时 父传入子的参数通过watch 监听请求接口时 watch 时而监听不到 请求接口的参数就不对 如图 父组件这么引入子组件v-show 和v-if 是有区别的 2 子组件通过watch 监听后 清空页面要展示的列表数据 重新从第一页加载数据&#x…

微服务sleuth+zipkin——链路追踪

一、链路追踪&#x1f349; 1.什么是链路追踪&#xff1f;&#x1f95d; 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多模块。这些模块负责不同的功能&#xff0c;组合成系统&#xff0c;最终可以提供丰富的功能。在这种架构中&#xff0c;一次请求往往需要…

简单了解UML类图

前言 大话设计中&#xff0c;多次使用UML类图来表示&#xff0c;并也给了基本的介绍&#xff0c;这里从书中选出UML图和代码做成笔记&#xff0c;以方便查找。 1、类 注意前面的符号&#xff1a; &#xff1a;public -&#xff1a;private #&#xff1a;protected 抽象类&…

在阿里云linux上安装MySql数据库

我们先远程连接服务器 然后输入 sudo yum update重新运行一下 然后 sudo yum install mysql-server安装 mysql 服务 其中有两次 y n 选择 都选y就好了 然后 运行 sudo service mysqld start启动MySql 然后 我们查看一下MySql sudo service mysqld status

Debian 12上如何关闭nobody共享文件夹,一个能让INSCODE AI 创作助手不知所措的小问题

这个问题之前在Debian 10和11上都没有遇到过&#xff0c;换上Debian 12后Samba的设置就出现了状况&#xff0c;装上Samba后什么都没有设置就在局域网可以看到&#xff1a; 根据之前的经验在/etc/samba/smb.conf里查了很久也没有看出所以然来&#xff0c;后来又问了INSCODE AI…

[洛谷]P8662 [蓝桥杯 2018 省 AB] 全球变暖(dfs)

读题不规范&#xff0c;做题两年半&#xff01; 注意&#xff1a;被海水淹没后的陆地应用另一个字符表示&#xff0c;而不是把它变为海洋&#xff0c;这个点可以便利&#xff0c;但不能被当作起点&#xff0c;不然就只有 36 分。 ACocde: #include<bits/stdc.h> using…

静态数码管显示

学习芯片&#xff1a; EP4CE6F17C8 本次学习使用的为共阴极数码管&#xff0c;即用低电平点亮数码管&#xff0c;同样可知&#xff0c;共阳极数码管的阳极连在一起&#xff0c;即用高电平点亮数码管。 八段数码管示意图&#xff1a; a,b,c,d,e,f,g,dg表示八段数码管时&#…

微服务sleuth+zipkin---链路追踪+nacos配置中心

目录 1.分布式链路追踪 1.1.链路追踪Sleuth介绍 1.2.如何完成sleuth 1.3.zipkin服务器 2.配置中心 2.1.常见配置中心组件 2.2.微服务集群共享一个配置文件 2.2.1实时刷新--配置中心数据 2.2.2.手动写一个实时刷新的配置类 ----刷新配置文件 2.3.多个微服务公用一个配…

FPGA——点亮led灯

文章目录 一、实验环境二、实验任务三、实验过程3.1 编写verliog程序3.2 引脚配置 四、仿真4.1 仿真代码4.2仿真结果 五、实验结果六、总结 一、实验环境 quartus18.1 vscode Cyclone IV开发板 二、实验任务 每间隔1S实现led灯的亮灭&#xff0c;实现流水灯的效果。 三、实…

测试开发面试你需要知道的

面试前的准备&#xff1a; 1.简历优化 选择一个比较简洁明了的模板&#xff0c;简历中突出自己的技能和项目经验&#xff0c;项目经历最好按照时间倒叙阐述&#xff0c;描述清自己在项目中承担的职责&#xff0c;在这个职责中做的一些过程改进&#xff0c;效率提升的内容&…

Redis的缓存问题

说起Redis的缓存&#xff0c;我们知道前端发出的请求到后端&#xff0c;后端先从Redis中查询&#xff0c;如果查询到了则直接返回&#xff0c;如果Redis中未查询到&#xff0c;就去数据库中查询&#xff0c;如果数据库中存在&#xff0c;则返回结果并且更新到Redis缓存当中&…

Linux云服务器,docker compose文件部署多个jar,docker部署多模块boot项目

前提条件 Linux服务器 服务器已经安装docker docker已经安装jdk镜像 docker已经安装mysql镜像 将要部署的项目的jar包打包好&#xff0c;项目是多模块springboot项目 部署过程 项目是3个模块的Spring boot项目&#xff0c;打出来3个jar&#xff0c;将这些jar包拷贝到…

无线电音频-BPA600蓝牙协议分析仪名词解析

1 介绍 2 Baseband基带分析 (1)Delta 是什么含义? "Delta" 有多个含义,取决于上下文。以下是常见的几种含义: 希腊字母:Delta&#x

Halcon机器视觉-15种常用缺陷检测实例

一、Halcon 15种常用缺陷检测实例分享 缺陷检测是一种通过计算机视觉技术来检测产品制造过程中的缺陷的方法。该技术可以检测出产品表面的缺陷&#xff0c;如裂纹、凹陷、划痕、气泡等&#xff0c;并且可以实时监测和诊断制造过程中的问题。在制造业中&#xff0c;机器视觉缺陷…

提升内功之模拟实现库函数strlen/strncpy/strcmp/strcat/strstr/memcpy/memmove

strlenstrncpystrcmpstrcatstrstrmemcpymemmove strlen strlen函数的作用就是求字符串的首元素开始往后计算字符串的长度&#xff0c;直到’\0‘&#xff0c;但不会把\0的长度计算进去 #include<stdio.h>size_t Strlen(const char* src) {size_t count 0;while (*src ! …

Docker 安装 MongoDB开启认证,创建只读用户权限。

创建带认证的mongdb容器 docker run -itd --name mongo -p 27017:27017 mongo --auth --auth 就是开启mongodb权限认证。如果不加 --auth 则是无权限认证&#xff0c;连接成功后任何用户都可以对数据库进行读写操作。 进入容器并创建用户 docker run -itd --name mongo -p 27…