Netty 源码解析 ——— ChannelConfig 和 Attribute

本文是Netty文集中“Netty 源码解析”系列的文章。主要对Netty的重要流程以及类进行源码解析,以使得我们更好的去使用Netty。Netty是一个非常优秀的网络框架,对其源码解读的过程也是不断学习的过程。

嗯,本文与其说是ChannelConfig、Attribute源码解析,不如说是对ChannelConfig以及Attribute结构层次的分析。因为这才是它们在Netty中使用到的重要之处。

ChannelConfig

Netty 源码解析 ——— 服务端启动流程 (下)中说过,当我们在构建NioServerSocketChannel的时候同时会构建一个NioServerSocketChannelConfig对象赋值给NioServerSocketChannel的成员变量config。

而这一个NioServerSocketChannelConfig是当前NioServerSocketChannel配置属性的集合。NioServerSocketChannelConfig主要用于对NioServerSocketChannel相关配置的设置(如,网络的相关参数配置),比如,配置Channel是否为非阻塞、配置连接超时时间等等。

下面我们来对NioServerSocketChannelConfig的结构做个详细介绍

NioServerSocketChannelConfig其实是一个ChannelConfig实例。ChannelConfig表示为一个Channel相关的配置属性的集合。所以NioServerSocketChannelConfig就是针对于NioServerSocketChannel的配置属性的集合。

ChannelConfig是Channel所需的公共配置属性的集合,如,setAllocator(设置用于channel分配buffer的分配器)。而不同类型的网络传输对应的Channel有它们自己特有的配置,因此可以通过扩展ChannelConfig来补充特有的配置,如,ServerSocketChannelConfig是针对基于TCP连接的服务端ServerSocketChannel相关配置属性的集合,它补充了针对TCP服务端所需的特有配置的设置setBacklog、setReuseAddress、setReceiveBufferSize。

DefaultChannelConfig作为ChannelConfig的默认实现,对ChannelConfig中的配置提供了默认值。

接下来,我们来看一个设置ChannelConfig的流程: serverBootstrap.option(ChannelOption.SO_REUSEADDR, true); 我们可以在启动服务端前通过ServerBootstrap来进行相关配置的设置,该选项配置会在Channel初始化时被获取并设置到Channel中,最终会调用底层ServerSocket.setReuseAddress方法来完成配置的设置。 ServerBootstrap的init()方法:

取出我们程序设定的options(即,LinkedHashMap对象),依次遍历options中的key-value对,将其设置到channel中。

首先对option和value进行校验,其实就是进行非空校验。 然后判断对应的是哪个常量属性,并进行相应属性的设置。如果传进来的ChannelOption不是已经设定好的常量属性,则会打印一条警告级别的日志,告知这是未知的channel option。 Netty提供ChannelOption的一个主要的功能就是让特定的变量的值给类型化。因为从’ChannelOption<T> option’和’T value’可以看出,我们属性的值类型T,是取决于ChannelOption的泛型的,也就属性值类型是由属性来决定的。

ChannelOption

这里,我们可以看到有个ChannelOption类,它允许以类型安全的方式去配置一个ChannelConfig。支持哪一种ChannelOption取决于ChannelConfig的实际的实现并且也可能取决于它所属的传输层的本质。

可见ChannelOption是一个Consant扩展类,Consant是Netty提供的一个单例类,它能安全去通过’==’来进行比较操作。通过ConstantPool进行管理和创建。 常量由一个id和name组成。id:表示分配给常量的唯一数字;name:表示常量的名字。

ConstantPool

如上所说,Constant是由ConstantPool来进行管理和创建的,那么ConstantPool又是个什么样的类了?

ConstantPool是Netty提供的一个常量池类,它底层通过一个成员变量constants来维护所有的常量:

constants:底层就是Java的ConcurrentHashMap对象。

并通过?的代码来实现的线程安全。主要通过ConcurrentHashMap的putIfAbsent来实现线程安全。

首先从constants中get这个name对应的常量,如果不存在则调用newConstant()来构建这个常量tempConstant,然后在调用constants.putIfAbsent方法来实现“如果该name没有存在对应的常量,则插入,否则返回该name所对应的常量。(这整个的过程都是原子性的)”,因此我们是根据putIfAbsent方法的返回来判断该name对应的常量是否已经存在于constants中的。如果返回为null,则说明当前创建的tempConstant就为name所对应的常量;否则,将putIfAbsent返回的name已经对应的常量值返回。(注意,因为ConcurrentHashMap不会允许value为null的情况,所以我们可以根据putIfAbsent返回为null则代表该name在此之前并未有对应的常量值)

ChannelOption类中属性

好了,到目前为止,我们已经知道ChannelOption是一个Constant的扩展,因此它可以由ConstantPool来管理和创建。接下来,我们继续来看看ChannelOption类中的一些重要属性:

正如我们前面所说的,这个ConstantPool<ChannelOption<Object>> pool(即,ChannelOption常量池)是ChannelOption的一个私有静态成员属性,用于管理和创建ChannelOption。

同时,ChannelOption中将所有的与相关的配置项名称都已常量形式定义好了。如:

这些定义好的ChannelOption常量都已经存储数到ChannelOption的常量池(ConstantPool)中了。

注意,ChannelOption本身并不维护选项值的信息,它只是维护选项名字本身。比如,“public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");”?这只是维护了“SO_RCVBUF”这个选项名字的信息,同时泛型表示选择值类型,即“SO_RCVBUF”选项值为Integer。

好了,到目前为止,我们对Netty的ChannelOption的设置以及底层的实现已经分析完了,简单的来说:Netty在初始化Channel时会构建一个ChannelConfig对象,而ChannelConfig是Channel配置属性的集合。比如,Netty在初始化NioServerSocketChannel的时候同时会构建一个NioServerSocketChannelConfig对象,并将其赋值给NioServerSocketChannel的成员变量config,而这个config(NioServerSocketChannelConfig)维护了NioServerSocketChannel的所有配置属性。比如,NioServerSocketChannelConfig提供了setConnectTimeoutMillis方法来设置NioServerSocketChannel连接超时的时间。 同时,程序可以通过ServerBootstrap或Boostrap的option(ChannelOption<T> option, T value)方法来实现配置的设置。这里,我们通过ChannelOption来实现配置的设置,ChannelOption中已经将常用的配置项预定义为了常量供我们直接使用,同时ChannelOption的一个主要的功能就是让特定的变量的值给类型化。因为从’ChannelOption<T> option’和’T value’可以看出,我们属性的值类型T,是取决于ChannelOption的泛型的,也就属性值类型是由属性来决定的。

Attribute

一个attribute允许存储一个值的引用。它可以被自动的更新并且是线程安全的。 其实Attribute就是一个属性对象,这个属性的名称为AttributeKey<T> key,而属性的值为T value。

我们可以通过程序ServerBootstrap或Boostrap的attr方法来设置一个Channel的属性,如: serverBootstrap.attr(AttributeKey.valueOf("userID"), UUID.randomUUID().toString()); 当Netty底层初始化Channel的时候,就会将我们设置的attribute给设置到Channel中:

大体的流程是同ChannelOption一样,这里就不过多赘述了。主要提及下一些关键点。

如上面所说,Attribute就是一个属性对象,这个属性的名称为AttributeKey<T> key,而属性的值为T value。 而AttributeKey也是Constant的一个扩展,因此也有一个ConstantPool来管理和创建,这和ChannelOption是类似的。

Channel类本身继承了AttributeMap类,而AttributeMap它持有多个Attribute,这些Attribute可以通过AttributeKey来访问的。所以,才可以通过channel.attr(key).set(value)的方式将属性设置到channel中了(即,这里的attr方法实际上是AttributeMap接口中的方法)。

AttributeKey、Attribute、AttributeMap间的关系: AttributeMap相对于一个map,AttributeKey相当于map的key,Attribute是一个持有key(AttributeKey)和value的对象。因此在map中我们可以通过AttributeKey key获取Attribute,从而获取Attribute中的value(即,属性值)。

关于ChannelHandlerContext.attr(..) 和 Channel.attr(..)

Q:ChannelHandlerContext和Channel都提供了attr方法,那么它们设置的属性作用域有什么不同了? A:在Netty 4.1版本之前,它们两设置的属性作用域确实存在着不同,但从Netty 4.1版本开始,它们两设置的属性的作用域已经完全相同了。

Netty 4.1版本新特性及注意点

从上面的描述上,我们可以知道从Netty 4.1 开始 “ChannelHandlerContext.attr(..) == Channel.attr(..)”。即放入它们的attribute的作用域是一样的了。每个Channel内部指保留一个AttributeMap。 而在Netty4.1之前,Channel内部保留有一个AttributeMap,而每个ChannelHandlerContext内部又保留有它们自己的AttributeMap,这样通过Channel.attr()放入的属性,是无法通过ChannelHandlerContext.attr()得到的,反之亦然。这种行为不仅令人困惑还会浪费内存。因此有了Netty 4.1将attr作用域统一的做法。

后记

若文章有任何错误,望大家不吝指教:)

参考

圣思园《精通并发与Netty》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 研究

Android跨进程通信IPC之4——AndroidIPC基础1

这里强烈建议把前面两篇文章看一遍,因为前面两篇文章对后面大家对android的IPC的理解帮助很大,本片文章主要内容如下

793
来自专栏Golang语言社区

Golang记录、计算函数执行耗时、运行时间的一个简单方法

先写一个公共函数, 比如在 common 包下有这么一个方法: // 写超时警告日志 通用方法 func TimeoutWarning(tag, detai...

3576
来自专栏嵌入式程序猿

ARM cortexM4异常处理(2)

上次课程我们简单讲解了异常的一些基础知识,希望对大家有所帮助,今天我们来看看异常在向量表中的位置,异常的入口和返回。 中断向量表 有人会问,不是讲异常吗,怎么...

3457
来自专栏PingCAP的专栏

十分钟成为 Contributor 系列 | 为 TiDB 重构 built-in 函数

为了加速表达式计算速度,最近我们对表达式的计算框架进行了重构,这篇教程为大家分享如何利用新的计算框架为 TiDB 重写或新增 built-in 函数。

1210
来自专栏nnngu

Java监听器Listener的使用详解

监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器...

37711
来自专栏喔家ArchiSelf

从构造函数看线程安全

线程是编程中常用而且强大的手段,在使用过程中,我们经常面对的就是线程安全问题了。对于Java中常见的数据结构而言,一般的,ArrayList是非线程安全的,Ve...

832
来自专栏java系列博客

Java面试通关要点汇总集基础篇之参考答案

1544
来自专栏Linux驱动

11.按键驱动之定时器防抖(详解)

本节目标:  通过定时器来防止按键抖动,测试程序是使用上节的:阻塞操作的测试程序 1.在没有定时器防抖情况下,按键没有稳定之前会多次进入中断,使得输出多个相...

18710
来自专栏木木玲

堆外内存 之 DirectByteBuffer 详解

2257
来自专栏CodingBlock

设计模式之单例模式

  无论什么开发中,设计模式都起着关键的作用,其中比较常用的当属单例了,所谓单例,就是让一个类在项目中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象...

2116

扫码关注云+社区