Netty源码分析第三章: 客户端接入流程

概述:

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

第一节:初始化NioSockectChannelConfig

在剖析接入流程之前我们首先补充下第一章有关创建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;
//
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有关的初始化过程剖析完成

上一节: 执行任务队列

下一节: 处理接入事件之handle的创建

Netty源码分析第3章(客户端接入流程)---->第1节: 初始化NioSockectChannelConfig的更多相关文章

  1. Netty源码分析第3章(客户端接入流程)---->第4节: NioSocketChannel注册到selector

    Netty源码分析第三章: 客户端接入流程 第四节: NioSocketChannel注册到selector 我们回到最初的NioMessageUnsafe的read()方法: public void ...

  2. Netty源码分析第3章(客户端接入流程)---->第5节: 监听读事件

    Netty源码分析第三章: 客户端接入流程 第五节: 监听读事件 我们回到AbstractUnsafe的register0()方法: private void register0(ChannelPro ...

  3. Netty源码分析第3章(客户端接入流程)---->第2节: 处理接入事件之handle的创建

    Netty源码分析第三章: 客户端接入流程 第二节: 处理接入事件之handle的创建 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端 ...

  4. Netty源码分析第3章(客户端接入流程)---->第3节: NioSocketChannel的创建

    Netty源码分析第三章: 客户端接入流程 第三节: NioSocketChannel的创建 回到上一小节的read()方法: public void read() { //必须是NioEventLo ...

  5. Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾

    Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...

  6. Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程

    Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...

  7. Netty源码分析第6章(解码器)---->第1节: ByteToMessageDecoder

    Netty源码分析第六章: 解码器 概述: 在我们上一个章节遗留过一个问题, 就是如果Server在读取客户端的数据的时候, 如果一次读取不完整, 就触发channelRead事件, 那么Netty是 ...

  8. Netty源码分析第4章(pipeline)---->第1节: pipeline的创建

    Netty源码分析第四章: pipeline 概述: pipeline, 顾名思义, 就是管道的意思, 在netty中, 事件在pipeline中传输, 用户可以中断事件, 添加自己的事件处理逻辑, ...

  9. Netty源码分析第4章(pipeline)---->第2节: handler的添加

    Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...

随机推荐

  1. python第十六课——ascii码

    2.ascii码 美国设计出来的一张编码表,将涉及的字符都编号了,底层仍然还是进行二进制的运算: 记住:3个范围段 1).'0' --> 码值:48 2).'A' --> 码值:65 3) ...

  2. oc的静态函数static

    oc的静态函数与类函数不同: 1.静态函数与c++中表现一致,只在模块内部可见: 2.静态函数内部没有self变量: 3.静态函数不参与动态派发:没有在函数列表里:是静态绑定的: @implement ...

  3. android小游戏模版—重力感应

               好久没更新博客了,今天来谈谈android小游戏---重力感应,一般在游戏里运用的比較多,比方这类游戏有:神庙逃亡.极品飞车,平衡球.三围重力迷宫,重力赛车等. 首先什么是重力感 ...

  4. javascript花式理解闭包

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  5. HBase学习之路 (六)过滤器

    过滤器(Filter) 基础API中的查询操作在面对大量数据的时候是非常苍白的,这里Hbase提供了高级的查询方法:Filter.Filter可以根据簇.列.版本等更多的条件来对数据进行过滤,基于Hb ...

  6. Day14 集合(一)

    集合总体介绍 Java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等.Java集合工具包位置是java.util.*Java集合主要可以划分为4个部分:List列 ...

  7. mysql安装后找不到sock文件

    mysql rpm安装方式之后,启动找不到sock文件,经排查是之前安装的由mysql, 需要时yum list installed mysql 或者  rpm -qa |grep -i mysql ...

  8. codewars-7kyu:Sum of the first nth term of Series

    Task: Your task is to write a function which returns the sum of following series upto nth term(param ...

  9. Spring事务(一)JDBC方式下的事务使用示例

    摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.创建数据表结构 二.创建对应数据表的PO 三.创建表与实体间 ...

  10. PAT乙级1026

    1026 程序运行时间 (15 分)   要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时 ...