写点什么

Netty 常量池

  • 2021 年 11 月 11 日
  • 本文字数:3784 字

    阅读完需:约 12 分钟

在传统的 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>>() {@Overrideprotected 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 方法而已, 可以看到, 每


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


一个常量对象都有一个 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 是我们自定义的值


先来说说这个 AttributeKey 吧, 跟 ChannelOption 是类似的, ChannelOption 用来表示配置的 key, 而 AttributeKey 则是用来表示 DefaultAttributeMap 中的 key, 联想 ChannelOption, 可以知道 AttributeKey 中一定也会有一个匿名内部类实现了 ConstantPool 接口, 即:

评论

发布
暂无评论
Netty常量池