Netty源码解读(二)Netty中的buffer

Netty详解之整体架构 1. 概述Netty是JBoss出品的高效的Java NIO开发框架,本文将主要分析Netty实现方面的东西。Netty总体架构图: 2. Bufferorg.jboss.netty.buffer包的接口及类的结构图如下: 2.1 Channel Buffer的种类Netty使用ChannelBuffer来存储并操作读写的网络数据。ChannelBuffer除了提供和ByteBuffe... 阅读详情

上一篇文章我们概要介绍了Netty的原理及结构,下面几篇文章我们开始对Netty的各个模块进行比较详细的分析。Netty的结构最底层是buffer模块,这部分也相对独立,我们就先从buffer讲起。

What: buffer二三事

buffer中文名又叫缓冲区,按照维基百科的解释,是”在数据传输时,在内存里开辟的一块临时保存数据的区域”。它其实是一种化同步为异步的机制,可以解决数据传输的速率不对等以及不稳定的问题。

根据这个定义,我们可以知道涉及I/O(特别是I/O写)的地方,基本会有buffer的存在。就Java来说,我们非常熟悉的Old I/O–InputStream&OutputStream系列API,基本都是在内部使用到了buffer。Java课程老师就教过,outputStream.write()只将内容写入了buffer,必须调用outputStream.flush(),才能保证数据写入生效!

而NIO中则直接将buffer这个概念封装成了对象,其中最常用的大概是ByteBuffer了。于是使用方式变为了:将数据写入Buffer,flip()一下,然后将数据读出来。于是,buffer的概念更加深入人心了!

Netty中的buffer也不例外。不同的是,Netty的buffer专为网络通讯而生,所以它又叫ChannelBuffer(好吧其实没有什么因果关系…)。我们下面就来讲讲Netty中的buffer。当然,关于Netty,我们必须讲讲它的所谓”Zero-Copy-Capable”机制。

When & Where: TCP/IP协议与buffer

TCP/IP协议是目前的主流网络协议。它是一个多层协议,最下层是物理层,最上层是应用层(HTTP协议等),而在Java开发中,一般只接触TCP以上,即传输层和应用层的内容。这就是Netty的主要应用场景。

TCP报文有个比较大的特点,就是它传输的时候,会先把应用层的数据项拆开成字节,然后按照自己的传输需要,选择合适数量的字节进行传输。什么叫”自己的传输需要”?首先TCP包有最大长度限制,那么太大的数据项肯定是要拆开的。其次因为TCP以及下层协议会附加一些协议头信息,如果数据项太小,那么可能报文大部分都是没有价值的头信息,这样传输是很不划算的。因此有了收集一定数量的小数据,并打包传输的Nagle算法(这个东东在HTTP协议里会很讨厌,Netty里可以用setOption(“tcpNoDelay”, true)关掉它)。

这么说可能太抽象了一点,我们举个例子吧:

发送时,我们这样分3次写入(‘|’表示两个buffer的分隔):

   +-----+-----+-----+
   | ABC | DEF | GHI |
   +-----+-----+-----+

接收时,可能变成了这样:

   +----+-------+---+---+
   | AB | CDEFG | H | I |
   +----+-------+---+---+

很好懂吧?可是,说了这么多,跟buffer有个什么关系呢?别急,我们来看下面一部分。

Why: buffer中的分层思想

我们先回到之前的`messageReceived`方法:

1 public void messageReceived(
2         ChannelHandlerContext ctx, MessageEvent e) {
3     // Send back the received message to the remote peer.
4     transferredBytes.addAndGet(((ChannelBuffer) e.getMessage()).readableBytes());
5     e.getChannel().write(e.getMessage());
6 }

这里MessageEvent.getMessage()默认的返回值是一个ChannelBuffer。我们知道,业务中需要的”Message”,其实是一条应用层级别的完整消息,而一般的buffer工作在传输层,与”Message”是不能对应上的。那么这个ChannelBuffer是什么呢?

来一个官方给的图,我想这个答案就很明显了:

这里可以看到,TCP层HTTP报文被分成了两个ChannelBuffer,这两个Buffer对我们上层的逻辑(HTTP处理)是没有意义的。但是两个ChannelBuffer被组合起来,就成为了一个有意义的HTTP报文,这个报文对应的ChannelBuffer,才是能称之为”Message”的东西。这里用到了一个词”Virtual Buffer”,也就是所谓的”Zero-Copy-Capable Byte Buffer”了。是不是顿时觉得豁然开朗了?

我这里总结一下,如果要说NIO的Buffer和Netty的ChannelBuffer最大的区别的话,就是前者仅仅是传输上的Buffer,而后者其实是传输Buffer和抽象后的逻辑Buffer的结合。延伸开来说,NIO仅仅是一个网络传输框架,而Netty是一个网络应用框架,包括网络以及应用的分层结构。

当然,使用ChannelBuffer表示”Message”,不失为一个比较实用的方法,但是使用一个对象来表示解码后的Message可能更符合习惯一点。在Netty里,MessageEvent.getMessage()是可以存放一个POJO的,这样子抽象程度又高了一些,这个我们在以后讲到ChannelPipeline的时候会说到。

How: Netty中的ChannelBuffer及实现

好了,终于来到了代码实现部分。之所以啰嗦了这么多,因为我觉得,关于”Zero-Copy-Capable Rich Byte Buffer”,理解为什么需要它,比理解它是怎么实现的,可能要更重要一点。

关于代码阅读,我想可能很多朋友跟我一样,喜欢”顺藤摸瓜”式读代码–找到一个入口,然后顺着查看它的调用,直到理解清楚。很幸运,ChannelBuffers(注意有s!)就是这样一根”藤”,它是所有ChannelBuffer实现类的入口,它提供了很多静态的工具方法来创建不同的Buffer,靠“顺藤摸瓜”式读代码方式,大致能把各种ChannelBuffer的实现类摸个遍。先列一下ChannelBuffer相关类图。

此外还有WrappedChannelBuffer系列也是继承自AbstractChannelBuffer,图放到了后面。

ChannelBuffer中的readerIndex和writerIndex

Netty中的buffer是完全重新实现的,与NIO ByteBuffer与ByteBuffer不同的是,它内部保存了一个读指针readerIndex和一个写指针writerIndex,可以同时进行读和写,而不需要使用flip()进行读写切换。AbstactChannelBuffer类里面包含了主要的读写逻辑,贴一段代码,让大家能看的更明白一点:

01 public void writeByte(int value) {
02 setByte(writerIndex ++, value);
03 }
04  
05 public byte readByte() {
06 if (readerIndex == writerIndex) {
07 throw new IndexOutOfBoundsException("Readable byte limit exceeded: "
08 + readerIndex);
09 }
10 return getByte(readerIndex ++);
11 }
12  
13 public int writableBytes() {
14 return capacity() - writerIndex;
15 }
16  
17 public int readableBytes() {
18 return writerIndex - readerIndex;
19 }

这里readerIndex总是小于writerIndex。我觉得这样的方式非常自然,比单指针与flip()要更加好理解一些。AbstactChannelBuffer还有两个相应的mark指针markedReaderIndex和markedWriterIndex,跟NIO的原理一样,作标记用,这里不再赘述了。

字节序Endianness与HeapChannelBuffer

HeapChannelBuffer是最常用的Buffer,跟NIO HeapByteBuffer作用相当,其底层也是一个byte[]。

HeapChannelBuffer有两个子类:BigEndianHeapChannelBuffer和LittleEndianHeapChannelBuffer。这里有个很基础的概念:字节序(ByteOrder/Endianness)。字节序规定了多于一个字节的数字(int啊long什么的),如何在内存中表示。BIG_ENDIAN(大端序)表示高位在前,按照大端序,整型数12会被存储为0 0 0 12这样四个字节,而LITTLE_ENDIAN则正好相反。可能搞C/C++的程序员对这个会比较熟悉,而Javaer则比较陌生一点,因为Java已经把内存给管理好了。但是在网络编程方面,根据协议的不同,不同的字节序也可能会被用到。目前大部分协议还是采用大端序,可参考RFC1700。

了解了这些知识,我们也很容易就知道为什么会有BigEndianHeapChannelBuffer和LittleEndianHeapChannelBuffer了。

DynamicChannelBuffer

DynamicChannelBuffer是一个很方便的Buffer,之所以叫Dynamic是因为它的长度会根据内容的长度来扩充,你可以像使用ArrayList一样,无须关心其容量。DynamicChannelBuffer实现自动扩容的核心在于ensureWritableBytes方法,算法很简单:在写入前做容量检查,容量不够时,新建一个容量x2的buffer,跟ArrayList的扩容是相同的。贴一段代码吧(为了代码易懂,这里我删掉了一些边界检查,只保留主逻辑):

01 public void writeByte(int value) {
02     ensureWritableBytes(1);
03     super.writeByte(value);
04 }
05  
06 public void ensureWritableBytes(int minWritableBytes) {
07     if (minWritableBytes <= writableBytes()) {
08         return;
09     }
10  
11     int newCapacity = capacity();
12     int minNewCapacity = writerIndex() + minWritableBytes;
13     while (newCapacity < minNewCapacity) {
14         newCapacity <<= 1;
15     }
16  
17     ChannelBuffer newBuffer = factory().getBuffer(order(), newCapacity);
18     newBuffer.writeBytes(buffer, 0, writerIndex());
19     buffer = newBuffer;
20 }

CompositeChannelBuffer

CompositeChannelBuffer是由多个ChannelBuffer组合而成的,可以看做一个整体进行读写。这里有一个技巧:CompositeChannelBuffer并不会开辟新的内存并直接复制所有ChannelBuffer内容,而是直接保存了所有ChannelBuffer的引用,并在子ChannelBuffer里进行读写,从而实现了”Zero-Copy-Capable”。来段简略版的代码,应该更能说明其原理:

01 public class CompositeChannelBuffer{
02  
03     //components保存所有内部ChannelBuffer
04     private ChannelBuffer[] components;
05     //indices记录在整个CompositeChannelBuffer中,每个components的起始位置
06     private int[] indices;
07     //缓存上一次读写的componentId
08     private int lastAccessedComponentId;
09  
10     public byte getByte(int index) {
11         //通过indices中记录的位置索引到对应第几个子Buffer
12         int componentId = componentId(index);
13         return components[componentId].getByte(index - indices[componentId]);
14     }
15  
16     public void setByte(int index, int value) {
17         int componentId = componentId(index);
18         components[componentId].setByte(index - indices[componentId], value);
19     }
20  
21 }

查找componentId的算法再次不作介绍了,大家自己实现起来也不会太难。值得一提的是,基于ChannelBuffer连续读写的特性,使用了顺序查找(而不是二分查找),并且用lastAccessedComponentId来进行缓存。

ByteBufferBackedChannelBuffer

前面说ChannelBuffer是自己的实现的,其实只说对了一半。ByteBufferBackedChannelBuffer就是封装了NIO ByteBuffer的类,用于实现堆外内存的Buffer(使用NIO的DirectByteBuffer)。当然,其实它也可以放其他的ByteBuffer的实现类。代码实现就不说了,也没啥可说的。

WrappedChannelBuffer

WrappedChannelBuffer都是几个对已有ChannelBuffer进行包装,完成特定功能的类。代码不贴了,实现都比较简单,列一下功能吧。

类名入口功能
SlicedChannelBufferChannelBuffer.slice()
ChannelBuffer.slice(int,int)
某个ChannelBuffer的一部分
TruncatedChannelBufferChannelBuffer.slice()
ChannelBuffer.slice(int,int)
某个ChannelBuffer的一部分, 可以理解为其实位置为0的SlicedChannelBuffer
DuplicatedChannelBufferChannelBuffer.duplicate()与某个ChannelBuffer使用同样的存储, 区别是有自己的index
ReadOnlyChannelBufferChannelBuffers
.unmodifiableBuffer(ChannelBuffer)
不可变的buffer

至此Netty 3.7的buffer部分我们基本了解了,相关内容还是比较简单的,也没有太多费脑细胞的地方。

Netty 4.0之后就不同了,ChannelBuffer改名ByteBuf,成为了单独项目buffer,并且为了性能优化,加入了BufferPool之类的机制,已经变得比较复杂了(本质倒没怎么变)。性能优化是个比较复杂的事情,研究源码时,建议先避开这些东西,了解其整体结构,等到需要深入时再对算法进行细致研究。举个例子,Netty4.0里为了优化,将Map换成了Java 8里6000行的ConcurrentHashMapV8,你们感受一下…

【netty】Netty堆外内存泄露排查盛宴 遇到堆外内存泄露不要怕,仔细耐心分析,总能找到思路,要多看日志,多分析。如果使用了 Netty 堆外内存,那么可以自行监控堆外内存的使用情况,不需要借助第三方工具,我们是使用的“反射”拿到的堆外内存的情况。逐渐缩小范围,直到 Bug 被找到。当我们确认某个线程的执行带来 Bug 时,可单步执行,可二分执行,定位到某行代码之后,跟到这段代码,然后继续单步执行或者二分的方式来定位最终出 Bug 的代码。这个方法屡试不爽,最后总能找到想要的 Bug。 阅读详情

相关推荐

Netty源码解析-概述篇

本文是由code4craft发表在博客上的,原文基于Netty3.7的版本,源码部分对buffer、Pipeline、Reactor模式等进行了部分讲解,个人又继续新增了后续的几个核心组件的源码解读,新增了具体的案例。 Netty的源码非常好,质量极高,是Java中质量最高的开源项目之一,(比Spring系列源码高几层楼…) 我十分建议大家花上一周时间自习读一读。 概述 Netty是什么 大概用N...

微信搜:import_bigdata,大数据领域硬核原创作者 1028

(四)Netty之Buffer缓冲区

基本介绍 缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机智,能够跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer Buffer类及其子类 1.在NIO中,Buffer是一个顶层父类,它是一个抽象类,类的层级关系图: 常用Buffer子类一览 ByteBuffer,存储字节数据到缓冲区 ShortBuffer,存

qq_38949960的博客 1942

SAM3模型国内快速上手:Modelscope权重下载+环境配置一条龙指南

本文详细介绍了SAM3模型在国内的快速部署方案,包括通过Modelscope平台下载权重文件、优化CUDA 11.8环境配置以及实战避坑技巧。针对国内开发者常见的网络问题和环境兼容性问题,提供了从权重下载到模型加载的全流程指南,帮助用户高效完成部署并快速上手SAM3模型。

weixin_28465009的博客 1144

Netty进阶篇二、关于Buffer内存

文章目录一、堆内内存与堆外内存二、内存对象初始化三、内存数据读写四、内存释放五、Cleaner机制和Finalizer机制六、JAVA四种引用类型七、章节总结 === 楼兰 === ​ 这一节,将重点来讨论NIO中的Buffer。彻底理解清楚Buffer是个什么样的东西。 一、堆内内存与堆外内存 ​ 关于Buffer,在基础篇中已经做过简单介绍。他是网络IO数据与本地数据的缓冲。Nio中相关数据都是通过Buffer来携带。在这一部分,就来深入看看这个Buffer到底是什么。 ​ 实际上Buff

roykingw的专栏 1154

上网第二课:办了百兆宽带,为什么测速只有一半?

最慢的那一环。

qq_36765170的博客 108

【实战指南】Green Hills MULTI-IDE 从零安装到工程创建全流程

本文详细介绍了Green Hills MULTI-IDE从零安装到工程创建的全流程,包括系统要求检查、安装步骤、工程配置及调试技巧。特别适合嵌入式开发新手快速上手这款专业IDE工具,提升开发效率。

weixin_28740299的博客 474

Netty——Buffer的原理与实战

1、rewind()方法 Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。 2、clear() 与 compact() 方法 一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。 如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buff.

庄小焱 1335

PCL 点云拟合 最小二乘法拟合平面

在点云处理领域,最小二乘法(Least Squares)拟合是一种常用的数学方法,用于估计数据点的最佳拟合曲线或平面。平面拟合广泛应用于点云数据处理中的地面检测、物体表面分析等任务。 本文将介绍如何使用PCL库实现基于最小二乘法的平面拟合算法,通过拟合点云数据中的平面,来提取出数据的平面部分。

纵马踏花向自由 1026

Netty源码解读 Netty中的buffer

What: buffer二三事 buffer中文名又叫缓冲区,按照维基百科的解释,是”在数据传输时,在内存里开辟的一块临时保存数据的区域”。它其实是一种化同步为异步的机制,可以解决数据传输的速率不对等以及不稳定的问题。 根据这个定义,我们可以知道涉及I/O(特别是I/O写)的地方,基本会有buffer的存在。就Java来说,我们非常熟悉的Old I/O–InputStream&Outp

u010395804的专栏 843

单片机应用案例:ESP32 搭配 BH1750 光强传感器和 Web 服务器

在该应用案例中,我们将使用 ESP32 开发板作为主控制器,BH1750 光强传感器用于采集环境光强度数据,并通过 Web 服务器展示采集到的数据。步骤一:购买材料步骤二:连接硬件步骤三:编写代码我们将使用 Arduino IDE 编写代码,以下是主要代码:// WiFi 设置const char* ssid = “你的 WiFi 名称”;const char* password = “密码”;// Web 服务器实例// BH1750 实例// 获取当前光强度数据。

RlDart的博客 813

北斗导航 | ILS(仪表着陆系统)、MLS(微波着陆系统)和GBAS(地基增强系统)的性能对比

最 早 的 GBAS 系 统 是 **局 域 增 强 系 统(LAAS)**,它是 1990 年由美国 Honeywell 公司 和 NASA 共 同 完 成 的 差 分 GPS(DGPS)演 变 而 来 的。1998 年,LAAS 被 ICAO 采 纳为 GBAS。2009 年 9 月 3 日 Honeywell 公 司 的GBAS 系统通过了 **CAT-I 精密进近着陆系统**的设计认证 ,而 **CAT-II 和 CAT-III 系统的研发和验证工作也正在有序的进行中**

尘世冰封的专栏 3985

bilibili源码_BIO到NIO源码的一些事儿之NIO 下 Buffer解读 上

前言此系列文章会详细解读NIO的功能逐步丰满的路程,为Reactor-Netty 库的讲解铺平道路。 关于Java编程方法论-Reactor与Webflux的视频分享,已经完成了Rxjava 与 Reactor,b站地址如下: Rxjava源码解读与分享:https://www.bilibili.com/video/av34537840 Reactor源码解读与分享:https://www....

weixin_32780623的博客 298

华为OD新系统机试真题 4.26 -最大化游戏试玩资格分发

华为OD机试新系统真题【最大化游戏试玩资格分发】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD新系统机试 4月26号真题【最大化游戏试玩资格分发】。

qq_45776114的博客 825

netty源码分析

netty源码解析

qq_39203337的博客 3916

kylinos-SP3的软件包降级到V10-SP1中的版本

如果想再次回退到原来版本,将repo改为原来的SP3版本,然后执行yum history 查看ID号,执行yum history rollback ID就可以恢复了。此时我们发现已安装的版本是1.32-2.ky10,而sp1网络源里的版本是1.30-11.ky10,我们将降级安装网络源中的低版本软件包。近期有个客户想要把kylinos-V10-sp3的软件包降级到kylinos-V10-SP1的版本。3、以tar包为例,查看已安装版本及网络源中其他版本。1、查看系统版本,确认是V10-SP3。

太极淘的博客 1103

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Netty 核心源码解读 —— ServerBootstrap 篇

本文我们就开始对 ServerBootstrap 进行源码解读(4.1.51.Final-SNAPSHOT),为什么是 ServerBootstrap,记得在用 Netty 做第一个项目...

vivisran的博客 507

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

python netty_Netty源码解读(一)概述

感谢网友【黄亿华】投递本稿。Netty和Mina是Java世界非常知名的通讯框架。它们都出自同一个作者,Mina诞生略早,属于Apache基金会,而Netty开始在Jboss名下,后来出来自立门户netty.io。关于Mina已有@FrankHui的Mina系列文章,我正好最近也要做一些网络方面的开发,就研究一下Netty的源码,顺便分享出来了。 Netty目前有两个分支:4.x和3.x。4.0分...

weixin_39601794的博客 529

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

netty 校验_Netty 核心源码解读 —— ServerBootstrap 篇

本文我们就开始对 ServerBootstrap 进行源码解读(4.1.51.Final-SNAPSHOT),为什么是 ServerBootstrap,记得在用 Netty 做第一个项目的时候,写的第一行 Code 就是new ServerBootstrap(),ServerBootstrap 是 Netty Server 的启动类,所以从它开始了解 Netty 是最合适的。Serve...

weixin_39634898的博客 138
上一篇: Netty源码解读(一)概述
下一篇: Netty源码解读(三)Channel与Pipeline
kobejayandy
博客等级 码龄18年 2853粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值