Netty分布式客户端接入流程初始化源码分析


Posted in Java/Android onMarch 25, 2022

前文概述:

之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐进, 带大家继续剖析客户端接入之后的相关逻辑

第一节:初始化NioSockectChannelConfig

创建channel

在剖析接入流程之前我们首先补充下第一章有关创建channel的知识:

我们在第一章剖析过channel的创建, 其中NioServerSocketChannel中有个构造方法:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

当时我们并没有剖析config相关知识, 在这一章首先对此做一个补充, 这里我们看到每一个NioServerSocketChannel都拥有一个config属性, 这个属性存放着NioServerSocketChannel的相关配置, 这里创建一个NioServerSocketChannelConfig对象, 并将当前channel, 和channel对应的java底层的socket对象进行了传入, NioServerSocketChannelConfig其实是NioServerSocketChannel的内部类

我们跟到NioServerSocketChannelConfig类的构造方法中:

private NioServerSocketChannelConfig(NioServerSocketChannel channel, ServerSocket javaSocket) {
    super(channel, javaSocket);
}

我们继续跟入其父类DefaultServerSocketChannelConfig的构造方法中:

public DefaultServerSocketChannelConfig(ServerSocketChannel channel, ServerSocket javaSocket) {
    super(channel);
    if (javaSocket == null) {
        throw new NullPointerException("javaSocket");
    }
    this.javaSocket = javaSocket;
}

这里继续调用了其父类的构造方法, 并保存了jdk底层的socket对象, 并且调用其父类DefaultChannelConfig的构造方法

跟到其父类DefaultChannelConfig的构造方法中

public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

这里调用了自身的构造方法, 传入了channel和一个AdaptiveRecvByteBufAllocator对象

AdaptiveRecvByteBufAllocator是一个缓冲区分配器, 用于分配一个缓冲区Bytebuf的, 有关Bytebuf的相关内容会在后面的章节详细讲解, 这里可以简单介绍作为了解, 就当对于之后知识的预习

Bytebuf相当于jdk的ByetBuffer, Netty对其做了重新的封装, 用于读写channel中的字节流, 熟悉Nio的同学对此应该并不陌生, AdaptiveRecvByteBufAllocator就是用于分配netty中ByetBuff的缓冲区分配器, 根据名字, 我们不难看出这个缓冲区是一个可变大小的字节缓冲区

我们跟到AdaptiveRecvByteBufAllocator的构造方法中:

public AdaptiveRecvByteBufAllocator() {
    //DEFAULT_MINIMUM:最小缓冲区长度64字节
    //DEFAULT_INITIAL:初始容量1024字节
    //最大容量65536字节
    this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
}

这里调用自身的构造方法并且传入了三个属性, 这三个属性的含义分别为:

DEFAULT_MINIMUM:代表要分配的缓冲区长度最少为64个字节

DEFAULT_INITIAL:代表要分配的缓冲区的初始容量为1024个字节

DEFAULT_MAXIMUM:代表要分配的缓冲区最大容量为65536个字节

我们跟到this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM)方法中

public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
    //忽略验证代码
    //最小容量在table中的下标
    int minIndex = getSizeTableIndex(minimum);
    if (SIZE_TABLE[minIndex] < minimum) {
        this.minIndex = minIndex + 1;
    } else {
        this.minIndex = minIndex;
    }
    //最大容量在table中的下标
    int maxIndex = getSizeTableIndex(maximum);
    if (SIZE_TABLE[maxIndex] > maximum) {
        this.maxIndex = maxIndex - 1;
    } else {
        this.maxIndex = maxIndex;
    }
    this.initial = initial;
}

其中这里初始化了三个属性, 分别是:

minIndex:最小容量在size_table中的下标

maxIndex:最大容量在table中的下标

initial:初始容量1024个字节

这里的size_table就是一个数组, 里面盛放着byteBuf可分配的内存大小的集合, 分配的bytebuf无论是扩容还是收缩, 内存大小都属于size_table中的元素, 那么这个数组是如何初始化的, 我们跟到这个属性中:

private static final int[] SIZE_TABLE;

我们看到是一个final修饰的静态成员变量, 我们跟到static块中看它的初始化过程:

static {
    //List集合
    List<Integer> sizeTable = new ArrayList<Integer>();
    //从16开始, 每递增16添加到List中, 直到大于等于512
    for (int i = 16; i < 512; i += 16) {
        sizeTable.add(i);
    }
    //从512开始, 倍增添加到List中, 直到内存溢出
    for (int i = 512; i > 0; i <<= 1) {
        sizeTable.add(i);
    }
    //初始化数组
    SIZE_TABLE = new int[sizeTable.size()];
    //将list的内容放入数组中
    for (int i = 0; i < SIZE_TABLE.length; i ++) {
        SIZE_TABLE[i] = sizeTable.get(i);
    }
}

首先创建一个Integer类型的list用于盛放内存元素

这里通过两组循环为list添加元素

首先看第一组循环:

for (int i = 16; i < 512; i += 16) {
    sizeTable.add(i);
}

这里是通过16平移的方式, 直到512个字节, 将每次平移之后的内存大小添加到list中

再看第二组循环

for (int i = 512; i > 0; i <<= 1) {
    sizeTable.add(i);
}

超过512之后, 再通过倍增的方式循环, 直到int类型内存溢出, 将每次倍增之后大小添加到list中

最后初始化SIZE_TABLE数组, 将list中的元素按下表存放到数组中

这样就初始化了内存数组

再回到AdaptiveRecvByteBufAllocator的构造方法中

public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
    //忽略验证代码
    //最小容量在table中的下标
    int minIndex = getSizeTableIndex(minimum);
    if (SIZE_TABLE[minIndex] < minimum) {
        this.minIndex = minIndex + 1;
    } else {
        this.minIndex = minIndex;
    }
    //最大容量在table中的下标
    int maxIndex = getSizeTableIndex(maximum);
    if (SIZE_TABLE[maxIndex] > maximum) {
        this.maxIndex = maxIndex - 1;
    } else {
        this.maxIndex = maxIndex;
    }
    this.initial = initial;
}

这里分别根据传入的最小和最大容量去SIZE_TABLE中获取其下标

我们跟到getSizeTableIndex(minimum)中:

private static int getSizeTableIndex(final int size) {
    for (int low = 0, high = SIZE_TABLE.length - 1;;) {
        if (high < low) {
            return low;
        }
        if (high == low) {
            return high;
        }
        int mid = low + high >>> 1;
        int a = SIZE_TABLE[mid];
        int b = SIZE_TABLE[mid + 1];
        if (size > b) {
            low = mid + 1;
        } else if (size < a) {
            high = mid - 1;
        } else if (size == a) {
            return mid;
        } else {
            return mid + 1;
        }
    }
}

这里是通过二分查找去获取其下表

if (SIZE_TABLE[minIndex] < minimum)这里判断最小容量下标所属的内存大小是否小于最小值, 如果小于最小值则下标+1

最大容量的下标获取原理同上, 判断最大容量下标所属内存大小是否大于最大值, 如果是则下标-1

我们回到DefaultChannelConfig的构造方法:

public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

刚才我们剖析过了AdaptiveRecvByteBufAllocator()的创建过程, 我们继续跟到this()中:

protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
    setRecvByteBufAllocator(allocator, channel.metadata());
    this.channel = channel;
}

我们看到这里初始化了channel, 在channel初始化之前, 调用了setRecvByteBufAllocator(allocator, channel.metadata())方法, 顾名思义, 这是用于设置缓冲区分配器的方法, 第一个参数是我们刚刚分析过的新建的AdaptiveRecvByteBufAllocator对象, 第二个传入的是与channel绑定的ChannelMetadata对象, ChannelMetadata对象是什么?

我们跟进到metadata()方法当中, 由于是channel是NioServerSocketChannel, 所以调用到了NioServerSocketChannel的metadata()方法:

public ChannelMetadata metadata() {
    return METADATA;
}

这里返回了一个成员变量METADATA, 跟到这个成员变量中:

private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);

这里创建了一个ChannelMetadata对象, 并在构造方法中传入false和16

继续跟到ChannelMetadata的构造方法中

public ChannelMetadata(boolean hasDisconnect, int defaultMaxMessagesPerRead) {
    //省略验证代码
    //false
    this.hasDisconnect = hasDisconnect;
    //16
    this.defaultMaxMessagesPerRead = defaultMaxMessagesPerRead;
}

这里做的事情非常简单, 只初始化了两个属性:

hasDisconnect=false

defaultMaxMessagesPerRead=16

defaultMaxMessagesPerRead=16代表在读取对方的链接或者channel的字节流时(无论server还是client), 最多只循环16次, 后面的讲解将会看到

剖析完了ChannelMetadata对象的创建, 我们回到DefaultChannelConfig的构造方法:

protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
    setRecvByteBufAllocator(allocator, channel.metadata());
    this.channel = channel;
}

跟到setRecvByteBufAllocator(allocator, channel.metadata())方法中:

private void setRecvByteBufAllocator(RecvByteBufAllocator allocator, ChannelMetadata metadata) {
    if (allocator instanceof MaxMessagesRecvByteBufAllocator) {
        ((MaxMessagesRecvByteBufAllocator) allocator).maxMessagesPerRead(metadata.defaultMaxMessagesPerRead());
    } else if (allocator == null) {
        throw new NullPointerException("allocator");
    }
    rcvBufAllocator = allocator;
}

首先会判断传入的缓冲区分配器是不是MaxMessagesRecvByteBufAllocator类型的, 因为AdaptiveRecvByteBufAllocator实现了MaxMessagesRecvByteBufAllocator接口, 所以此条件成立

之后将其转换成MaxMessagesRecvByteBufAllocator类型,

然后调用其maxMessagesPerRead(metadata.defaultMaxMessagesPerRead())方法,

这里会走到其子类DefaultMaxMessagesRecvByteBufAllocator的maxMessagesPerRead(int maxMessagesPerRead)方法中,

其中参数metadata.defaultMaxMessagesPerRead()返回就是ChannelMetadata的属性defaultMaxMessagesPerRead,

也就是16

跟到maxMessagesPerRead(int maxMessagesPerRead)方法中:

public MaxMessagesRecvByteBufAllocator maxMessagesPerRead(int maxMessagesPerRead) {
    //忽略验证代码

    //初始化为16
    this.maxMessagesPerRead = maxMessagesPerRead;
    return this;
}

这里将自身属性maxMessagesPerRead设置为16, 然后返回自身

回到DefaultChannelConfig的构造方法

private void setRecvByteBufAllocator(RecvByteBufAllocator allocator, ChannelMetadata metadata) {
    if (allocator instanceof MaxMessagesRecvByteBufAllocator) {
        ((MaxMessagesRecvByteBufAllocator) allocator).maxMessagesPerRead(metadata.defaultMaxMessagesPerRead());
    } else if (allocator == null) {
        throw new NullPointerException("allocator");
    }
    rcvBufAllocator = allocator;
}

设置完了内存分配器的maxMessagesPerRead属性, 最后将DefaultChannelConfig自身的成员变量rcvBufAllocator设置成我们初始化完毕的allocator对象

至此, 有关channelConfig有关的初始化过程剖析完成

以上就是Netty分布式客户端接入流程初始化源码分析的详细内容,更多关于Netty分布式客户端接入流程初始化的资料请关注三水点靠木其它相关文章!

Java/Android 相关文章推荐
分享一些Java的常用工具
Jun 11 Java/Android
SpringBoot生成License的实现示例
Jun 16 Java/Android
SpringBoot集成Redis,并自定义对象序列化操作
Jun 22 Java/Android
gateway网关接口请求的校验方式
Jul 15 Java/Android
java中用float时,数字后面加f,这样是为什么你知道吗
Sep 04 Java/Android
Spring中的使用@Async异步调用方法
Nov 01 Java/Android
Java如何实现通过键盘输入一个数组
Feb 15 Java/Android
Java9新特性对HTTP2协议支持与非阻塞HTTP API
Mar 16 Java/Android
剑指Offer之Java算法习题精讲二叉树专项训练
Mar 21 Java/Android
Spring Boot DevTools 全局配置学习指南
Mar 31 Java/Android
Jmerte 分布式压测及分布式压测配置
Apr 30 Java/Android
Springboot中如何自动转JSON输出
Jun 16 Java/Android
java后台调用接口及处理跨域问题的解决
Mar 24 #Java/Android
SpringBoot中使用Redis作为全局锁示例过程
Mar 24 #Java/Android
java项目构建Gradle的使用教程
Mar 24 #Java/Android
SpringBoot2零基础到精通之数据与页面响应
MybatisPlus EntityWrapper如何自定义SQL
Mar 22 #Java/Android
SpringBoot2零基础到精通之数据库专项精讲
Mar 22 #Java/Android
Spring Bean是如何初始化的详解
Mar 22 #Java/Android
You might like
PHP 作用域解析运算符(::)
2010/07/27 PHP
支持生僻字且自动识别utf-8编码的php汉字转拼音类
2014/06/27 PHP
php+mysql大量用户登录解决方案分析
2014/12/29 PHP
php遍历目录下文件并按修改时间排序操作示例
2019/07/12 PHP
OfflineSave离线保存代码再次发布使用说明
2007/05/23 Javascript
慎用 somefunction.prototype 分析
2009/06/02 Javascript
学习面向对象之面向对象的基本概念:对象和其他基本要素
2010/11/30 Javascript
js的表单操作 简单计算器
2011/12/29 Javascript
在javascript中对于DOM的加强
2013/04/11 Javascript
浅析用prototype定义自己的方法
2013/11/14 Javascript
javascript实现数字+字母验证码的简单实例
2014/02/10 Javascript
js 针对html DOM元素操作等经验累积
2014/03/11 Javascript
超级简单实现JavaScript MVC 样式框架
2015/03/24 Javascript
纯javascript响应式树形菜单效果
2015/11/10 Javascript
深入学习AngularJS中数据的双向绑定机制
2016/03/04 Javascript
使用jQuery5分钟快速搞定双色表格的简单实例
2016/08/08 Javascript
关于javascript的一些知识以及循环详解
2016/09/12 Javascript
jQuery Dialog 打开时自动聚焦的解决方法(两种方法)
2016/11/24 Javascript
Angular移动端页面input无法输入的解决方法
2017/11/14 Javascript
vue通过video.js解决m3u8视频播放格式的方法
2019/07/30 Javascript
微信小程序中data-key属性之数据传输(经验总结)
2020/08/22 Javascript
微信小程序视频弹幕发送功能的实现
2020/12/28 Javascript
Python中用memcached来减少数据库查询次数的教程
2015/04/07 Python
Python编程实现的简单神经网络算法示例
2018/01/26 Python
详解Python3网络爬虫(二):利用urllib.urlopen向有道翻译发送数据获得翻译结果
2019/05/07 Python
使用Django清空数据库并重新生成
2020/04/03 Python
HTML5边玩边学(2)基础绘图实现方法
2010/09/21 HTML / CSS
意大利专业化妆品品牌:KIKO MILANO
2017/02/01 全球购物
行政部总经理岗位职责
2014/01/04 职场文书
土木工程师职业规划范文
2014/03/07 职场文书
优秀教师自我评价范文
2014/09/27 职场文书
大学生学习新党章思想汇报
2014/10/25 职场文书
2015年师德师风承诺书
2015/01/22 职场文书
上市公司财务总监岗位职责
2015/04/03 职场文书
数学备课组工作总结
2015/08/12 职场文书
python自动获取微信公众号最新文章的实现代码
2022/07/15 Python