导航:首页 > 源码编译 > netty读事件源码

netty读事件源码

发布时间:2023-01-14 16:33:05

⑴ [Netty源码分析]ByteBuf(一)

ByteBuf通过两个指针协助读写操作,读操作使用readerIndex,写操作使用writerIndex.

readerIndex、writerIndex初始值是0,写入数据时writerIndex增加,读取数据时readerIndex增加,但是readerIndex不会超过writerIndex.

读取之后,0-readerIndex之间的空间视为discard的,调用discardReadByte方法可以释放这一部分空间,作用类似于ByteBuffer的compact方法.readerIndex-writerIndex之间的数据是可读的,等价于ByteBuffer中position-limit之间的数据.

writerIndex-capacity之间的空间是可写的,等价于ByteBuffer中limit-capacity之间的空间.

读只影响readerIndex、写只影响writerIndex,读写之间不需要调整指针位置,所以相较于NIO的ByteBuffer,可以极大的简化读写操作

调用discardReadBytes会发生字节数组的内存复制,所以频繁调用会导致性能下降

ByteBuf对write操作进行了封装,有ByteBuf的write操作负责进行剩余咳哟好难过空间的校验,如果可用缓冲区不足,ByteBuf会自动进行动态扩展。对于使用者而言不需要关心底层的校验和扩展细节,只需要不超过capacity即可

对缓冲区进行读操作时,有的时候我们需要对之前的操作进行回滚,读操作并不会改变缓冲区的内容,回滚主要是重新设置索引信息

Mark:将当前的位置指针被分到mark变量中

Reset:恢复位置指针为mark中的变量值

ByteBuf有readerIndex、writerIndex,所以有四个相应的方法

markReaderIndex: 将当前readerIndex备份到markedReaderIndex中

resetReaderIndex: 将当前readerIndex设置为markedReaderIndex

markWriterIndex: 将当前readerIndex备份到markedWriterIndex中

resetWriterIndex: 将当前readerIndex设置为markedWriterIndex

3)slice:
返回当前ByteBuf的可读子缓冲区,即从readerIndex到writerIndex的ByteBuf,返回的ByteBuf和原有缓冲区共享内容,但是维护独立的索引.当修改其中一个ByteBuf的内容时,另一个也会改变,即双方持有的是同一个对象的引用

常见类:

相比于PooledHeapByteBuf,UnpooledHeapByteBuf的实现更加简单,也不容易出现内存管理的问题,所以才性能满足的情况下,推荐使用UnpooledHeapByteBuf

在I/O通信线程的读写缓冲区中使用DirectByteBuf,后端业务消息的编码使用HeapByteBuf,这样的组合性能最优

⑵ Netty源码笔记

Netty版本4.0.29.Final,以构造客户端连接服务端的角度来追踪源码

NioEventLoopGroup的构造器中会调用父类MultithreadEventLoopGroup的构造器

在父类MultithreadEventExecutorGroup的构造器中

上面已经完成了NioEventLoop的创建,并保存在NioEventLoopGroup的数组属性上。而关于NioEventLoop在具备线程池能力时,在何时启动已经创建的线程呢?在SingleThreadEventExecutor::execute中可以找到答案

而关于NioEventLoop内线程启动后的逻辑,可以在创建该线程时看到有向线程提交一个任务。根据任务内SingleThreadEventExecutor.this.run()可以定位到创建线程提交任务的NioEventLoop::run

NioEventLoop::run是Netty的核心所在。它是NioEventLoop的线程执行的唯一任务。方法内无限循环,阻塞等待IO事件或队列中的任务

关于队列中的任务从哪里来?一是源码内部,启动时会直接提交任务到队列中;二是可以直接取出channel中的NioEventLoop向其提交任务;三是使用Channel写数据时,都是以任务的形式提交到队列中。与Channel绑定的NioEventLoop循环时会消费提交到队列中任务并执行,后续在分析unsafe时会一并提及。

客户端Bootstrap配置引导时,需要指定Channel类型,后续会使用反射创建该Channel类型实例。

客户端完成NioEventLoopGroup和Bootstrap的创建及对Bootstrap进行相关的设置后,使用Bootstrap尝试与服务端建立连接。在同步与异步之间,推荐使用异步来监听连接事件的结果。

AbstractBootstrap是Bootstrap的父类,initAndRegister中完成了通道的创建、初始化以及注册

group()取到开始配置的NioEventLoopGroup,register在父类MultithreadEventLoopGroup中

虽说暂时不引入unsafe类逻辑,但在unsafe::register中,通道注册完成后会调用管道的fireChannelRegistered方法,进而执行自定义的ChannelInitializer,最终形成完整的管道。
目前管道上链表有三个节点:head、自定义ChannelInitializer、tail(规定自头向尾的流向为In,自尾向头的为Out)
它们的类型都是AbstractChannelHandlerContext,每个Context上都持有对应的ChannelHandler

至此,Channel的初始化及注册已经完成。回到Bootstrap::doConnect中,在Channel注册这个异步任务完成后,开始真正的连接服务端

channel连接建立完成后,可以使用channel写数据

写数据不会直接发送数据到服务端,而是会缓存起来,直到调用flush才会完成数据的最终发送。flush逻辑与写数据一样,channel到pipeline,tail到head。最终交由unsafe类来完成

以客户端建立连接发送数据流程为例(服务端大部分逻辑相似),总结Netty的工作流程:

⑶ Netty核心技术及源码剖析-Netty入站与出站机制

1、Netty的组件设计: Netty的主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
2、ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不过它是用来处理出站数据的。
3、ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务器端的数据会通过pipeline中的一些列ChannelOutboundHandler,并被这些Handler处理,反之则称为入站的。

1、当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节。
2、Netty提供一些列实用的编解码器,他们都实现了ChannelInboundHandler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

1、关系继承图

2、由于不可能知道远程节点是否会一次性发送一个完整的信息,tcp有可能出现粘包拆包的问题,这个类会对入站数据进行缓冲,知道它准备好被处理。
3、一个关于ByteToMessageDecoder实例分析

⑷ netty源码debug 解析

1.  EventLoopGroup bossGroup =newNioEventLoopGroup() 创建线程组

       看一下 EventExecutorGroup 接口 依赖关系

       它的主要的方法是next 方法

看一下 newNioEventLoopGroup 依赖关系

2 创建newNioEventLoopGroup 它的时候方法

2.1 重点看一下 SelectProvider.provider ,同时看一下类SelectProvider 这个类的作用

可参考  SelectProvider类解析

2.2

看一下 这个类  KQueueSelectorImpl的解析参考 的构造方法 KQueueSelectorImpl(SelectorProvider var1)

IOUtil.makePipe(false);是一个static native方法,所以我们没办法查看源码。但是我们可以知道该函数返回了一个非堵塞的管道(pipe),底层是通过Linux的pipe系统调用实现的;创建了一个管道pipe并返回了一个64为的long型整数,该数的高32位存放了该管道读端的文件描述符,低32位存放了该pipe的写端的文件描述符。

3.完成Selector和Channel 绑定的在 Channel的 initAndRegister 

因为在group 中完成reactor线程模型的同事 注入了Selector 选择器 group中的对象EventLoopGroup是包含Selector的 这就和我们的NIO模型

4 .看一下注册事件 

5 下面看一下 AbstractCHannel的register方法

6.看到register0(promise) 很高兴终于看到正真的注册的方法了

但是还需要看doRegister();

7.看看doRegister 里面的for循环做了什么

重点说一下这个方法

将NioServerSocketChannel注册到NioEventLoop的Selector上,this是对象NioServerSocketChannel 作为注册的附件 attachment  这样终于看到了 selector ,channel ,和attachment,NioEventLoop持有Selector对象在构造 reactor 线程模型的时候构造的,channel 是NioServerSocketChannel 是在初始化的时候构造的

8.接着上面的图看看pipeline.fireChannelRegistered()做了什么?

上图中的initChannel((C) ctx,chanel)完成了我们实际的 pipeline的注入 并且移除我们默认的defaultChannPipeline 这个是在我们创建channel的时候默认的,用了这么久现在可以remove,感叹设计的优秀呀。

到此完成了 selector  在reactor 模型中创建,channel 在 bind中创建 实例化,在上面看到了 Channel 和Selector的绑定,现在有看到了 pipeline的实例化。

9,pipeline的实例化 把所有的handel按照顺序放入其中。

10.构造处理chain 链表结构

到此完成实例化。

10.现在我要找到run 方法

11.此处的execute其实是父类的方法执行,

12.我们看到了startExecution

13.接下来看看 executor.execute

14 调用的是SingleThreadEventExecutor.this.run 其实是调用了NioEventLoop的run

15 看到了selectNow 和select 等 接着又是runallTasks

16.看看runallTasks

17.再把 pollTak() 方法看一下

⑸ Netty源码分析(七) PoolChunk

在分析源码之前,我们先来了解一下Netty的内存管理机制。我们知道,jvm是自动管理内存的,这带来了一些好处,在分配内存的时候可以方便管理,也带来了一些问题。jvm每次分配内存的时候,都是先要去堆上申请内存空间进行分配,这就带来了很大的性能上的开销。当然,也可以使用堆外内存,Netty就用了堆外内存,但是内存的申请和释放,依然需要性能的开销。所以Netty实现了内存池来管理内存的申请和使用,提高了内存使用的效率。
PoolChunk就是Netty的内存管理的一种实现。Netty一次向系统申请16M的连续内存空间,这块内存通过PoolChunk对象包装,为了更细粒度的管理它,进一步的把这16M内存分成了2048个页(pageSize=8k)。页作为Netty内存管理的最基本的单位 ,所有的内存分配首先必须申请一块空闲页。Ps: 这里可能有一个疑问,如果申请1Byte的空间就分配一个页是不是太浪费空间,在Netty中Page还会被细化用于专门处理小于4096Byte的空间申请 那么这些Page需要通过某种数据结构跟算法管理起来。
先来看看PoolChunk有哪些属性

Netty采用完全二叉树进行管理,树中每个叶子节点表示一个Page,即树高为12,中间节点表示页节点的持有者。有了上面的数据结构,那么页的申请跟释放就非常简单了,只需要从根节点一路遍历找到可用的节点即可。主要来看看PoolChunk是怎么分配内存的。

Netty的内存按大小分为tiny,small,normal,而类型上可以分为PoolChunk,PoolSubpage,小于4096大小的内存就被分成PoolSubpage。Netty就是这样实现了对内存的管理。
PoolChunk就分析到这里了。

⑹ Netty 源码解析 ——— ChannelConfig 和 Attribute

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

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

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

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()方法:

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

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

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

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

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

正如我们前面所说的,这个ConstantPool<ChannelOption<Object>> pool(即,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就是一个属性对象,这个属性的名称为AttributeKey<T> key,而属性的值为T value。

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

如上面所说,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(即,属性值)。

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

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

圣思园《精通并发与Netty》

⑺ Netty源码_NioEventLoop详解

这一章我们将讲解 netty 中真正使用的事件轮询器 NioEventLoop ,通过这一章,你将了解:

netty 的事件轮询器也是通过的 java nio 的选择器 Selector 来管理多个嵌套字 Socket 的通道 channel 。
那么选择器 Selector 是如何与通道 channel ,并管理它们的呢。

在 SelectableChannel 类用 register 方法将通道注册到选择器 Selector 中。

如果当前通道已经注册到给定的选择器 sel 上了,调用这个方法,更改了这个通道关注的 IO 事件类型 ops ,和绑定的对象 att 。
在 AbstractSelectableChannel 实现中我们看到:

选择键 SelectionKey 的功能并不复杂,主要是和通道 channel 的 IO 事件有关,分为四种:

选择器的主要功能就是获取已经准备好的通道 channel 。

一共有三个方法 int selectNow() , int select(long timeout) 和 int select() 。

Set<SelectionKey> selectedKeys() 方法返回准备好的选择键集合,通过选择键就可以得到对应的通道 channel 。

Selector wakeup() 可以唤醒被 select() 和 select(long timeout) 阻塞的等待线程。

NioEventLoop 的成员变量中有两个选择器实例 unwrappedSelector 和 selector

事件轮询器如何实现事件轮询的,就是主要看它的 run 方法实现:

我们知道事件轮询器要处理两种事件:通道的 IO 事件 和 任务(包括计划任务和待执行任务),那么就要合理分配时间:

NioEventLoop 基本逻辑已经说清楚了,我们知道它是如何平衡处理 IO 事件和 待执行的任务的。

⑻ Netty源码_NioEventLoopGroup详解

上一章 介绍 NioEventLoop 的实现原理,但是我们在 netty 一般都是直接使用 NioEventLoopGroup 类,直接创建一个事件轮询器组。

这个是事件轮询器组 EventExecutorGroup 实现的抽象基类。

这个 MultithreadEventExecutorGroup 类其实为事件轮询器组 EventExecutorGroup 奠定了基础啊。
在它的构造方法中,我们发现:

这个类没有啥可讲解的。

你会发现事件轮询器组 EventExecutorGroup 的实现非常简单,就是管理子的事件轮询器 EventLoop 。

阅读全文

与netty读事件源码相关的资料

热点内容
java和python交互 浏览:644
贵州网络服务器机柜云主机 浏览:265
未来番禺程序员待遇 浏览:211
安卓安智部落冲突密码怎么改 浏览:648
http协议单片机 浏览:73
pdfdocument 浏览:556
gcc编译vi文件 浏览:63
安卓连airpods怎么找耳机 浏览:927
加密货币转账教程 浏览:229
程序员小灰hashmap 浏览:838
国语pdf版 浏览:184
少儿编程作品美丽的小房子 浏览:974
服务器卡在网页上怎么办 浏览:54
用python自制编译器 浏览:951
android分享新浪微博客户端 浏览:26
系统中服务器在哪里下载地址 浏览:1001
新a4安卓手机怎么投屏 浏览:173
pdftoemf 浏览:886
java接口可以实现接口吗 浏览:59
vb编程10个随机函数 浏览:22