Netty实现原理浅析

一、总体结构

二、网络模型

Netty是典型的Reactor模型结构,关于Reactor的详尽阐释,可参考POSA2。Reactor模式的典型实现:

1、这是最简单的单Reactor单线程模型。Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际适用的不多。

2、相比上一种模型,该模型在处理器链部分才用了多线程(线程池),也是后端程序常用的模型。

3、第三种模型比起第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。

说完Reactor模型的三种形式,那么Netty是哪种呢?其实,还有一种Reactor模型的变种没说,那就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。在实现上,Netty中的Boss类充当mainReactor,NioWorker类充当subReactor(默认NioWorker的个数是Runtime.getRuntime().availableProcessors())。在处理新来的请求时,NioWorker读完已收到的数据到ChannelBuffer中,之后触发ChannelPipeline中的ChannelHandler流。

Netty是事件驱动的,可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在subReactor中同步的,所以如果业务处理handler耗时长,将严重影响可支持的并发数。这种模型适合于像Memcache这样的应用场景,但对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。Netty的可扩展性非常好,而像ChannelHandler线程池化的需要,可以通过在ChannelPipeline中添加Netty内置的ChannelHandler实现类-ExecutionHandler实现,对使用者来说只是添加一行代码而已。对于ExecutionHandler需要的线程池模型,Netty提供了两种可选:1)MemoryAwareThreadPoolExecutor可控制Executor中待处理任务的上限(超过上限时,后续进来的任务被阻塞),并可控制单个Channel待处理任务的上限;2)OrderedMemoryAwareThreadPoolExecutor 是  MemoryAwareThreadPoolExecutor 的子类,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序,但它并不保证同一Channel中的事件都在一个线程中执行(通常也没必要)。一般来 说,OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择,当然,如果有需要,也可以DIY一个。

四、buffer

org.jboss.netty.buffer包的接口及类的结构图如下:



该包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以简要的介绍。

Netty使用ChannelBuffer来存储并操作读写的网络数据。ChannelBuffer除了提供和ByteBuffer类似的方法,还提供了 一些实用方法,具体可参考其API文档。ChannelBuffer的实现类有多个,这里列举其中主要的几个:

1)HeapChannelBuffer:这是Netty读网络数据时默认使用的ChannelBuffer,这里的Heap就是Java堆的意思,因为 读SocketChannel的数据是要经过ByteBuffer的,而ByteBuffer实际操作的就是个byte数组,所以 ChannelBuffer的内部就包含了一个byte数组,使得ByteBuffer和ChannelBuffer之间的转换是零拷贝方式。根据网络字 节续的不同,HeapChannelBuffer又分为BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer,默认使用的是BigEndianHeapChannelBuffer。Netty在读网络 数据时使用的就是HeapChannelBuffer,HeapChannelBuffer是个大小固定的buffer,为了不至于分配的Buffer的 大小不太合适,Netty在分配Buffer时会参考上次请求需要的大小。

2)DynamicChannelBuffer:相比于HeapChannelBuffer,DynamicChannelBuffer可动态自适应大 小。对于在DecodeHandler中的写数据操作,在数据大小未知的情况下,通常使用DynamicChannelBuffer。

3)ByteBufferBackedChannelBuffer:这是directBuffer,直接封装了ByteBuffer的 directBuffer。

对于读写网络数据的buffer,分配策略有两种:1)通常出于简单考虑,直接分配固定大小的buffer,缺点是,对一些应用来说这个大小限制有时是不 合理的,并且如果buffer的上限很大也会有内存上的浪费。2)针对固定大小的buffer缺点,就引入动态buffer,动态buffer之于固定 buffer相当于List之于Array。

buffer的寄存策略常见的也有两种(其实是我知道的就限于此):1)在多线程(线程池) 模型下,每个线程维护自己的读写buffer,每次处理新的请求前清空buffer(或者在处理结束后清空),该请求的读写操作都需要在该线程中完成。 2)buffer和socket绑定而与线程无关。两种方法的目的都是为了重用buffer。

Netty对buffer的处理策略是:读 请求数据时,Netty首先读数据到新创建的固定大小的HeapChannelBuffer中,当HeapChannelBuffer满或者没有数据可读 时,调用handler来处理数据,这通常首先触发的是用户自定义的DecodeHandler,因为handler对象是和ChannelSocket 绑定的,所以在DecodeHandler里可以设置ChannelBuffer成员,当解析数据包发现数据不完整时就终止此次处理流程,等下次读事件触 发时接着上次的数据继续解析。就这个过程来说,和ChannelSocket绑定的DecodeHandler中的Buffer通常是动态的可重用 Buffer(DynamicChannelBuffer),而在NioWorker中读ChannelSocket中的数据的buffer是临时分配的 固定大小的HeapChannelBuffer,这个转换过程是有个字节拷贝行为的。

对ChannelBuffer的创建,Netty内部使用的是ChannelBufferFactory接口,具体的实现有 DirectChannelBufferFactory和HeapChannelBufferFactory。对于开发者创建 ChannelBuffer,可使用实用类ChannelBuffers中的工厂方法。

四、Channel

和Channel相关的接口及类结构图如下:

五、ChannelEvent

如前所述,Netty是事件驱动的,其通过ChannelEvent来确定事件流的方向。一个ChannelEvent是依附于Channel的 ChannelPipeline来处理,并由ChannelPipeline调用ChannelHandler来做具体的处理。下面是和 ChannelEvent相关的接口及类图:

对于使用者来说,在ChannelHandler实现类中会使用继承于ChannelEvent的MessageEvent,调用其 getMessage()方法来获得读到的ChannelBuffer或被转化的对象。

六、ChannelPipeline

Netty 在事件处理上,是通过ChannelPipeline来控制事件流,通过调用注册其上的一系列ChannelHandler来处理事件,这也是典型的拦截 器模式。下面是和ChannelPipeline相关的接口及类图:

事件流有两种,upstream事件和downstream事件。在ChannelPipeline中,其可被注册的ChannelHandler既可以 是 ChannelUpstreamHandler 也可以是ChannelDownstreamHandler ,但事件在ChannelPipeline传递过程中只会调用匹配流的ChannelHandler。在事件流的过滤器链 中,ChannelUpstreamHandler或ChannelDownstreamHandler既可以终止流程,也可以通过调用 ChannelHandlerContext.sendUpstream(ChannelEvent)或 ChannelHandlerContext.sendDownstream(ChannelEvent)将事件传递下去。下面是事件流处理的图示:


从上图可见,upstream event是被Upstream Handler们自底向上逐个处理,downstream event是被Downstream Handler们自顶向下逐个处理,这里的上下关系就是向ChannelPipeline里添加Handler的先后顺序关系。简单的理 解,upstream event是处理来自外部的请求的过程,而downstream event是处理向外发送请求的过程。

服务端处 理请求的过程通常就是解码请求、业务逻辑处理、编码响应,构建的ChannelPipeline也就类似下面的代码片断:

ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());

pipeline.addLast("handler", new MyBusinessLogicHandler());

其中,MyProtocolDecoder是ChannelUpstreamHandler类型,MyProtocolEncoder是 ChannelDownstreamHandler类型,MyBusinessLogicHandler既可以是 ChannelUpstreamHandler类型,也可兼ChannelDownstreamHandler类型,视其是服务端程序还是客户端程序以及 应用需要而定。

 

补充一点,Netty对抽象和实现做了很好的解耦。像org.jboss.netty.channel.socket包, 定义了一些和socket处理相关的接口,而org.jboss.netty.channel.socket.nio、 org.jboss.netty.channel.socket.oio等包,则是和协议相关的实现。

七、编码框架

对于请求协议的编码解码,当然是可以按照协议格式自己操作ChannelBuffer中的字节数据。另一方面,Netty也做了几个很实用的codec helper,这里给出简单的介绍。

1)FrameDecoder:FrameDecoder内部维护了一个 DynamicChannelBuffer成员来存储接收到的数据,它就像个抽象模板,把整个解码过程模板写好了,其子类只需实现decode函数即可。 FrameDecoder的直接实现类有两个:(1)DelimiterBasedFrameDecoder是基于分割符 (比如\r\n)的解码器,可在构造函数中指定分割符。(2)LengthFieldBasedFrameDecoder是基于长度字段的解码器。如果协 议 格式类似“内容长度”+内容、“固定头”+“内容长度”+动态内容这样的格式,就可以使用该解码器,其使用方法在API DOC上详尽的解释。
2)ReplayingDecoder: 它是FrameDecoder的一个变种子类,它相对于FrameDecoder是非阻塞解码。也就是说,使用 FrameDecoder时需要考虑到读到的数据有可能是不完整的,而使用ReplayingDecoder就可以假定读到了全部的数据。
3)ObjectEncoder 和ObjectDecoder:编码解码序列化的Java对象。
4)HttpRequestEncoder和 HttpRequestDecoder:http协议处理。

下面来看使用FrameDecoder和ReplayingDecoder的两个例子:

public class IntegerHeaderFrameDecoder extends FrameDecoder {
    protected Object decode(ChannelHandlerContext ctx, Channel channel,
            ChannelBuffer buf) throws Exception {
        if (buf.readableBytes() < 4) {
            return null;
        }
        buf.markReaderIndex();
        int length = buf.readInt();
        if (buf.readableBytes() < length) {
            buf.resetReaderIndex();
            return null;
        }
        return buf.readBytes(length);
    }
}

而使用ReplayingDecoder的解码片断类似下面的,相对来说会简化很多。

public class IntegerHeaderFrameDecoder2 extends ReplayingDecoder {
    protected Object decode(ChannelHandlerContext ctx, Channel channel,
            ChannelBuffer buf, VoidEnum state) throws Exception {
        return buf.readBytes(buf.readInt());
    }
}

就实现来说,当在ReplayingDecoder子类的decode函数中调用ChannelBuffer读数据时,如果读失败,那么 ReplayingDecoder就会catch住其抛出的Error,然后ReplayingDecoder接手控制权,等待下一次读到后续的数据后继 续decode。



【SCI复现】基于纳什博弈的多微网主体电热双层共享策略研究(Matlab代码实现)内容概要:本文围绕“基于纳什博弈的多微网主体电热双层共享策略研究”展开,结合Matlab代码实现,复现了SCI级别的科研成果。研究聚焦于多个微网主体之间的能源共享问题,引入纳什博弈理论构建双层优化模型,上层为各微网间的非合作博弈策略,下层为各微网内部电热联合优化调度,实现能源高效利用与经济性目标的平衡。文中详细阐述了模型构建、博弈均衡求解、约束处理及算法实现过程,并通过Matlab编程进行仿真验证,展示了多微网在电热耦合条件下的运行特性和共享效益。; 适合人群:具备一定电力系统、优化理论和博弈论基础知识的研究生、科研人员及从事能源互联网、微电网优化等相关领域的工程师。; 使用场景及目标:① 学习如何将纳什博弈应用于多主体能源系统优化;② 掌握双层优化模型的建模与求解方法;③ 复现SCI论文中的仿真案例,提升科研实践能力;④ 为微电网集群协同调度、能源共享机制设计提供技术参考。; 阅读建议:建议读者结合Matlab代码逐行理解模型实现细节,重点关注博弈均衡的求解过程与双层结构的迭代逻辑,同时可尝试修改参数或扩展模型以适应不同应用场景,深化对多主体协同优化机制的理解。
绘画教学机器人是一种借助现代科技辅助人们进行绘画活动的教学工具。 在当前这份资料中,我们重点阐述了基于Arduino开发板构建的绘画教学机器人,该设备运用图像识别和电机控制技术来完成自动绘画工作。 代码转载自:https://pan.quark.cn/s/128130bd7814 以下是本资料中的核心内容:1. Arduino及其在机器人中的应用:Arduino是一个开放源代码的电子原型平台,它包含一块能够执行输入/输出操作的电路板以及配套的编程系统,通常用于迅速构建交互式电子装置。 在本次项目中,Arduino充当机器人的核心部件,负责接收图像分析后的数据,并将这些数据转化为调控步进电机旋转的指令,进而引导笔架在白板上进行作画。 2. 图像识别技术:图像识别技术是指赋予计算机识别和处理图像中物体能力的技术手段。 本项目的图像识别功能由摄像头承担,它能够获取图像,并将彩色图像转化为灰度图像,再采用自适应阈值算法处理为二值图像。 随后,通过图像细化方法提取出二值图像的骨架信息,用以确定绘画的目标和路径。 3. 电机控制机制:电机控制是指借助电子技术对电机运行状态进行管理。 在本项目中,两个步进电机由Arduino进行控制实现精准的位置控制,从而达到绘画的目的。 步进电机的正转与反转动作能够驱动笔架部件,沿着预设的轨迹进行绘画。 4. 机器人设计要素:机器人的设计涵盖了图像处理单元、机械控制单元和图像处理算法。 机械单元的设计需要兼顾画笔的支撑构造,确保画笔的稳定性,并且能够适应不同的绘画速度和方向。 在硬件设计层面,选用了ULN2003驱动器来增强Arduino输出的信号,以驱动步进电机运转。 5. 所采用的技术工具与材料:项目中的主要硬件设备包括Arduino控制板、步进电机、ULN...
先展示下效果 https://pan.quark.cn/s/d8b64f900c05 在本文中,我们将详细研究Three.js库如何应用于构建点线几何空间图形特效,以及与HTML5 Canvas和几何空间相关的技术。 Three.js是一个基于WebGL的JavaScript库,它为开发者提供了一个便捷易用的接口来构建3D内容,可以在现代浏览器中运行,无需安装插件支持。 我们需要掌握Three.js中的基本概念。 Three.js的核心构成元素包括场景(Scene)、相机(Camera)和渲染器(Renderer)。 场景是3D世界的容纳单元,相机决定了观察3D世界的角度,而渲染器则负责将场景和相机整合成可视化的图像。 1. **Three.js的几何体**:在Three.js中,可以构建多种几何体,如BoxGeometry(立方体)、SphereGeometry(球体)和LineGeometry(线条)。 对于"点线几何空间图形特效",LineGeometry扮演着核心角色。 这种几何体允许开发者构建由一系列点构成的线段。 点可以被串联起来形成复杂的线性构造,这些构造可以进一步进行动画处理,以产生动态的视觉表现。 2. **材质(Material)**:赋予几何体色彩和质感的是材质。 在Three.js中,有多种材质类型,如MeshBasicMaterial、MeshLambertMaterial和LineBasicMaterial等。 对于点线效果,LineBasicMaterial通常会被选用,它能够设定线条的颜色、宽度和透明度等特征。 3. **着色器(Shader)**:为了实现更高级的效果,如光照、纹理和粒子系统,Three.js支持自定义着色器。 尽管"点线几何空间图形特效...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值