2024年最全Netty常量池_attributekey常量池(3),2024最新华为HarmonyOS鸿蒙校招面试题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

引入

Netty的源码分析中, 采用自底向上的方式进行分析, 底指的是一个个的组件, 上指的是整体的架构及一系列操作的执行流程举个简单的例子。
Netty服务端的启动流程中, 必然会调用bind方法, 一个bind方法的内部, 其实完成了许许多多的内容, 涉及了许多组件, 如果我们从入口开始讲起, 那么一遇到不认识的组件就容易陷入迷茫, 所以我们从基础组件开始讲起,。
最后再来讲Netty的启动流程, 这样, 在有了基础组件的知识的前提下, 再来看启动流程就会非常的清晰, 本篇文章讲的组件是Netty中的常量池

ChannelOption及常量池

在传统的BIO的编程中, 我们创建了一个ServerSocket后, 可以进行一些配置, 如下:
ServerSocket serverSocket = new ServerSocket( 8080 );
serverSocket.setReceiveBufferSize( 1024 * 64 );
serverSocket.setSoTimeout( 3000 );

这些配置相信大家也不会模式, ReceiveBufferSize表示接收缓冲区的大小, 根据javaDoc的描述, 还能规定为发送
方(即客户端)那边的发送窗口的大小(滑动窗口), 这些属于Tcp相关的内容了, 就不进行展开

到了Nio中, 引入了Channel, ServerSocket对应的就是ServerSocketChannel, 我们不会再去创建ServerSocket
那么还想对套接字进行上述配置应该怎么处理呢?在ServerSocketChannel中就有setOption方法来进行设置

再了解了这些后, 我们再回到Netty中, 大家在日常使用中, 貌似很少会直接用Nio中的ServerSocketChannel了, 因
为Netty又进一步进行了封装, 比如说新增了一个NioServerSocketChannel, 里面有两个属性, 一个是Nio中的
ServerSocketChannel, 一个是NioServerSocketChannelConfig, 很清晰, Netty利用NioServerSocketChannel
保存了ServerSocketChannel及其对应的配置NioServerSocketChannelConfig, 那大家很容易想到, 既然是Config
自然就会有很多的key-value来表示配置信息吧? 比如说: private Map<String, Object> config;

我要设置receiveBufferSize, 那么我就要config.put( “receiveBufferSize”, 1024 ); 这有一个弊端, 让使用
者需要手动输入字符串key, 而且, 如果key需要有些特殊的属性等信息, 也没法实现, 这些key通常是固定的, 也就是
我们所说的常量, 于是Netty为了能够让key拥有更多的功能, 而不是仅仅用字符串来表示, 就出现了ChannelOption
这个类, 我们来看看ChannelOption的继承关系及相关代码(继承关系如下图):

public interface Constant<T extends Constant<T>> extends Comparable<T> {
    int id();
    String name();
}

public abstract class AbstractConstant implements Constant{
    private final int id;
    private final String name;

    getter / setter / toString / hashCode / equals / compareTo
}

public class ChannelOption<T> extends AbstractConstant<ChannelOption<T>> {

    private static final ConstantPool<ChannelOption<Object>> pool = 
                                                    new ConstantPool<ChannelOption<Object>>() {
        @Override
        protected ChannelOption<Object> newConstant(int id, String name) {
            return new ChannelOption<Object>(id, name);
        }
    };

    public static <T> ChannelOption<T> valueOf(String name) {
        return (ChannelOption<T>) pool.valueOf(name);
    }

    public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");
    public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");
}

分析:
上面一共三个类, Constant是一个常量接口, 定义了id和name方法, AbstractConstant对Constant中的id和
name方法进行了实现, 其实就等于get方法而已, 可以看到, 每一个常量对象都有一个name, 这个name就是这个
常量对象的字符串表示形式, 更加具体一点, 之前我们设置到ServerSocketChannel中的receiveBufferSize这
个配置, 就对应一个常量对象, 常量对象的name就是receiveBufferSize, id是一个随机生成的值, 用来表示唯
一性, 在这里我们仅仅分析到了AbstractConstant, 抽象类, 定义了所有情况下的常量的公共信息id和name
再往后, 要配置Channel, 那么就出现了ChannelOption, 要配置数据库就可能会出现DatabaseOption(这一个
是为了让大家更好的理解常量的含义, 笔者臆想的)
于是ChannelOption表示用于Channel配置信息中的key, 我们看到这个ChannelOption的源码, 一开始出现了
一个pool, 池子, 就是本文的核心主题, 常量池, 这个池子的作用是用来保存常量的, 假设我有许多的Channel
配置, 那么我自然就需要有许多的ChannelOption, 由于配置的key一定是固定的, 那么在使用的时候, 如果要
更改配置信息, 总不能每次都new一个ChannelOption来表示配置信息的key吧, 于是, 这些key就被保存到了常量
池中, ConstantPool就是用来保存这些配置对应的key的, ConstantPool是一个抽象类, 里面就一个
ConcurrentMap来保存, ConcurrentMap的key一定是字符串, value是泛型, 泛型为ChannelOption的时候, 表
示这个常量池中保存的是ChannleOption, 泛型为DatabaseOption, 表示这个常量池中保存的是
DatabaseOption, 这个应该好理解, 不同类型的常量对象, 只需要在其类中创建一个ConstantPool就好了, 父
类的ConcurrentMap是公共需要的, 但是ConcurrentMap里面存的值却需要子类来定义, 同时实现newConstant
方法表示这个池子中常量的创建, 不知道说到这里大家会不会觉得有点绕, 我们以SO_RCVBUF这个ChannelOption
的创建过程及存储过程的源码进行一下分析吧

    public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF"), 通过valueOf作为
    入口进行创建, 可以联想到, 这个SO_RCVBUF就是常量池中ConcurrentMap的key, 与此同时会创建一个
    ChannelOption, 这个ChannelOption的name也是SO_RCVBUF, 我们再来看看ConstantPool的valueOf源码

    public T valueOf(String name) {
        checkNotNullAndNotEmpty(name);
        return getOrCreate(name);
    }

    private T getOrCreate(String name) {
        T constant = constants.get(name);
        if (constant == null) {
            final T tempConstant = newConstant(nextId(), name);
            constant = constants.putIfAbsent(name, tempConstant);
            if (constant == null) {
                return tempConstant;
            }
        }

        return constant;
    }

其实很简单, constants就是那个ConcurrentMap, 通过key为SO_RCVBUF去这个map查找对应的ChannelOption
如果没找到, 那么就调用newConstant创建一个该常量, 并且放入到ConcurrentMap中, newConstant由子类来
觉得创建的是哪个类型的常量, ChannelOption有一个匿名内部类是ConstantPool的子类, 之前我们也看过了,
其创建的是ChannelOption

总结:
任何配置都是一个key-value, ConstantPool用来保存任何配置中的key的对象表示形式, 其实就是用一个
ConcurrentMap来保存的, ConstantPool的子类来觉得这个Map中的value存储的是什么类型值, Channel的配置
对应的key用ChannelOption来表示, ChannelOption里面有一个name用来存储这个配置的字符串表示,
ChannelOption中利用一个匿名内部类继承于ConstantPool, 利用泛型指明了ConcurrentMap中保存的是
ChannelOption类型, 对于Channel中的SO_RCVBUF这个配置来说, 会创建一个ChannelOption,
ChannelOption中的name就是SO_RCVBUF, 与此同时将这个ChannelOption保存到常量池中

ChannelConfig

ChannelConfig, 故名思意了, 保存了Channel的配置, 比如说接收缓冲区的大小等, Netty中的Channel有很多类型,
对于Nio的是NioServerSocketChannel以及NioSocketChannel, 对于Bio的OioServerSocketChannel以及
OioSocketChannel, 不同类型的Channel配置自然就不一样, 如下图所示, 就是ChannelConfig的简单类图, 可以清
晰的看到, 在红线的两边刚好分为了两块, 一个是Nio的ChannelConfig, 一个是Bio的ChannelConfig, 都是用来存
储对应的Channel的配置信息的, 那到底怎么存呢?其实也没那么复杂, 就用一个Map存就好了, 比如说对于服务端需要
存储SO_RCVBUF这个配置信息, 正常情况应该是map.put( “SO_RCVBUF”, 1024 ), 但是在Netty中, 用
ChannelOption对象的方式来表示这个配置的key, 于是就变成了map.put( ChannelOption.SO_RCVBUF, 1024 ),
到此为止, 我们就将ChannelConfig - ChannelOption - ConstantPool的关系给描述完毕了

DefaultAttributeMap及AttributeKey

前面我们熟悉了ConstantPool与ChannelOption之间的关系, 以及ChannelConfig与ChannelOption之间的关系, 此
时再来看ConstantPool-AttributeKey-DefaultAttributeMap就会非常轻松了

我们在使用Netty的时候, 对ChannelHandler肯定是不会陌生的, 一个客户端与服务器的数据交互, 当数据到达了服
务端Netty程序的时候, 必然是通过多个ChannelHandler进行处理的, 比如先由LengthFieldPrepender这个
ChannelHandler对数据进行解码, 然后传给下一层级的ChannelHandler, 当数据写回客户端的时候, 同样会经过多
个ChannelHandler, 最后经过LengthFieldBasedFrameDecoder这个ChannelHandler进行编码并write回客户端,
这一整个生命周期中, 都是对一个SocketChannel进行操作, 那么假设想要从前一个ChannelHandler传递一些数据到
后一个ChannelHandler, 就需要将放置的数据存在到一个地方中, 所有的ChannelHandler都能够访问这块空间

DefaultAttributeMap就是这一块空间, 我们使用的NioServerSocketChannel就是这个类的子类,
NioServerSocketChannel之后的文章我们会进行分析, 那知道这两者关系的情况下, 我们就可以想到, 所有的
ChannelHandler中, 获取到对应的Channel, 就能访问这个Channel的共享空间DefaultAttributeMap, 可以往里面
写入key-value, 或者根据key读取value, 而这个key就是AttributeKey, value是我们自定义的值

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值