Netty开发实战(上篇共上中下3篇)_第1页
Netty开发实战(上篇共上中下3篇)_第2页
Netty开发实战(上篇共上中下3篇)_第3页
Netty开发实战(上篇共上中下3篇)_第4页
Netty开发实战(上篇共上中下3篇)_第5页
已阅读5页,还剩259页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

网络应用开发模型随着互联网技术的逐步推广,网络已经成为了人们生活和生产之中必不可少的重要组成,在一个如图所示的网络应用开发架构之中,除了需要有良好的网络硬件支撑之外,还需要有稳定可靠的软件与协议支持。OSI七层模型与TCP/IP模型网络应用的开发遵循OSI(OpenSystemInterconnection、开放系统互联通信)七层模型的设计要求,基于该模型可以在不同系统与不同网络之间实现可靠的数据通讯,图为读者列出了OSI七层模型。但是在实际的开发中由于应用层、表示层以及会话层都主要实现核心数据的发出,所以后续就产生了TCP/IP五层模型,由于TCP/IP只是实现数据的发出,这样就将数据链路层与物理层进行了简化,最终形成了TCP/IP四层模型。IO多路复用模型Java语言提供的网络开发包中已经为用户隐藏了具体协议的实现细节,开发者只需要关注具体的功能实现即可,图展示了当前主流的IO多路复用网络模型的定义,而这一开发模型也是Netty实现的核心基础。TCPTCP(TransmissionControlProtocol、传输控制协议)是一种面向连接的,可靠的,基于字节流的传输层通信协议,在OSI七层模型中主要完成第四层(传输层)所指定的功能。在网络数据通讯中,考虑到数据传输的稳定性往往会在数据前附加有一系列的头信息,图列出了TCP报文结构TCP报文结构源端口(16Bit):标识数据发送端应用层程序端口;目标端口(16Bit):标识数据接收方的应用程序端口序列号(32Bit):标识数据发送方所发出数据的编号,序列号以字节为单位,是一个随机数字;确认序列号(32Bit):标识数据接收方期望下次收到的数据编号;数据偏移(4Bit):保存数据段开始地址的偏移量;保留位(3Bit):内容必须为0;TCP报文结构标识符(9Bit),每个标志位的作用如下:NS(ECN-Nonce):ECN(ExplicitCongestionNotification、显式拥塞通知)是对TCP的扩展,定义于RFC3540(2003),ECN允许拥塞控制的端对端通知而避免丢包。ECN为一项可选功能,如果底层网络设施支持,则可能被启用ECN的两个端点使用。在ECN成功协商的情况下,ECN感知路由器可以在IP头中设置一个标记来代替丢弃数据包,以标明阻塞即将发生。数据包的接收端回应发送端的表示,降低其传输速率,就如同在往常中检测到包丢失那样。CWR(CongestionWindowReduced、拥塞窗口减少):该位内容为1时表示链路拥塞,会减少TCP窗口大小;ECE(ECN-Echo):主要用于TCP三次握手时,通过其表明一个TCP端是否具备有ECE功能;URG(Urgent、紧急):URG为1表示报文中有紧急数据需要优先处理,此时紧急指针数据项才会生效;ACK(Acknowledgement、应答):ACK为1时表示TCP报头中的确认序列号有效;PSH(Push、推送):PSH为1时表示数据不缓存,而是直接把接收到的数据推送给上层应用程序;RST(Reset、重置):RST为1表示存在有严重差错,需要重新建立TCP连接,此标记位还可以用于拒绝非法的报文段或拒绝连接请求;SYN(Synchronization、同步):SYN为1时表示建立请求连接时同步双方序列号;FIN(Finish、完成):FIN为1时表示数据已经发送完毕,请求关闭连接。TCP报文结构窗口大小(16Bit):用于表示数据接收方当前可以接收的最大数据量,单位是字节。窗口大小代表了数据接收方当前缓存区的大小,这样数据发送方就可以控制发送数据的长度,但是该大小会根据传输的拥塞情况进行调整;校验和(16Bit):数据发送方基于数据内容计算的一个数值,数据接收方要基于该数值进行消息接收的验证;紧急指针(16Bit):标记本报文段中紧急数据的最后一个字节序号;TCP报文结构可选项或填充项,每个选项开始的第1个字节为类型说明,包括如下几种选项类型:0:选项表配置结束(1字节);1:无操作,用于选项字段之间的字边界对齐(1字节);2:最大报文段长度配置,只能出现在同步报文段中(4字节),否则将被忽略;3:窗口扩大因子(3字节),取值范围“0-14”,只能出现在同步报文段中;4:SACKOK(SelectiveAcknowledgment、选择性确认):发送端只重新发送交互过程中丢失的包;5:包含了SACK实际工作选项,包括“是否支持SACK标记项”与“具体的SACK信息”;8:TCP时间戳(TCPTimestampsOption、TSopt,10字节),包括发送端的时间戳(TimestampValuefield、TSval,4字节)和时间戳回显应答(TimestampEchoReplyfield、TSecr,4字节);19:MD5摘要,将TCP伪首部、校验和为0的TCP首部、TCP数据段、通信双方约定的密钥(可选)计算出MD5摘要值并附加到该选项中,作为类似对TCP报文的签名;29:安全摘要,通过RFC5925引入,将“MD5摘要”的散列方法更换为SHA散列算法;TCP连接与关闭TCP协议的的运行可以分为三个阶段:连接创建(ConnectionEstablishment)、数据传送(DataTransfer)和连接终止(ConnectionTermination),而为了保证其数据传输的可靠性,在建立TCP连接时会采用三次握手机制,而在进行连接关闭时,就需要采用四次挥手机制TCP连接流程TCP连接流程:为了保证数据传输的可靠性,在建立TCP连接时会采用三次握手机制。服务器应用在启动时需要在特定的监听端口上进行服务的绑定,而后就可以等待客户端进程进行连接;TCP客户端向服务端发送连接请求的报文,此时报文首部的SYN位的内容为1(此时不会携带任何请求数据),并且要传递一个初始序号,随后TCP客户端进入到了一个同步请求已发送的状态;TCP服务端接收到客户端连接请求后,如果同意连接,则会发出确认报文,并且为自己初始化一个新的序号(此时的报文不包含数据),随后将该报文发送给客户端并进入到请求已接收的状态;TCP客户端收到确认报文后,还需要向服务端发送确认该报文收到的信息,此时将建立TCP连接,并实现数据通讯;TCP连接采用三次握手机制,最重要的一点是为了防止在不稳定的网络环境中,服务端会重复收到连接请求。例如,现在的网络环境暂时不稳定,有可能导致TCP客户端发送的第一次请求连接报文未发送成功,所以在TCP客户端长时间未收到TCP服务端连接确认报文后,TCP客户端会重新发送第二次请求连接报文。这样就导致服务端有可能同时收到两份TCP客户端的连接请求,如果此时的操作机制为“二次握手”(没有最后一次TCP客户端确认报文的发出),则会创建两个TCP连接,从而造成资源的浪费。采用三次握手机制后,需要TCP客户端发送确认TCP服务端的报文之后,TCP服务端才会在有效的连接请求中创建TCP连接。TCP连接关闭流程考虑到连接关闭前,TCP服务端还会向TCP客户端发送数据的需要,所以采用了四次挥手的处理机制将确认报文与完结报文分别发送TCP客户端发出关闭连接的请求报文(此时FIN标记为1,不会发送数据),并停止向TCP服务端发送数据,随后进入等待释放的状态;TCP服务端接收到关闭连接的请求报文后会向客户端发出确认报文,随后TCP服务端进入到关闭等待的状态中,此时TCP客户端已经不再向TCP服务端发送任何数据了,但是TCP服务端依然可以发送数据给TCP客户端;TCP客户端收到TCP服务端的确认请求报文后,在接收完TCP服务端发送来的数据之后,客户端将进入到等待释放状态的第2阶段,此时将等待TCP服务端发送关闭连接的报文;TCP服务端将最后的数据发送完毕后,就会向TCP客户端发送关闭连接的报文(FIN标记为1),由于TCP服务端在关闭连接前有可能向TCP客户端发送了一些数据,所以此时的TCP服务端将进入到最后确认的状态。TCP客户端接收到TCP服务端发送的关闭报文后,需要向TCP服务端发送确认报文,随后TCP客户端将进入到时间等待的状态;TCP服务端收到了TCP客户端连接的释放报文后,将进入到关闭状态(服务端一般要早于客户端关闭);TCP协议还在持续发展TCP是一个复杂但同时又在不断发展的通讯协议,在TCP协议发展中会收到大量RFC(RequestForComments、征求意见),并根据这些RFC的提议进行修改RFC1122:《因特网对主机的要求》阐明了许多TCP协议的实现要求;RFC2581:《TCP的拥塞控制》是一篇近年来关于TCP的很重要的RFC,描述了更新后的避免过度拥塞的算法。写于2001年的RFC3168描述了对明显拥塞的报告,这是一种拥塞避免的信号量机制。最近,一个新协议已经被加州理工学院的科研人员开发出来,命名为FASTTCP(基于快速活动队列管理的规模可变的传输控制协议)。它使用排队延迟作为拥塞控制信号;但是因为端到端的延迟通常不仅仅包括排队延迟,所以FASTTCP在实际互联网中的能否工作仍然是一个没有解决的问题。NettyNetty是一个基于JavaNIO基础的网络编程开发框架,可以帮助Java开发人员,快速实现异步的、基于事件驱动的网络应用程序,该框架由JBoss提供,开发者可以通过netty.io获取该开源项目的信息Netty创始人Netty的创始人是韩国人TrustinLee,1980年出生。8岁起在MSX迷你计算机上编写BASIC程序,爱好游戏编程以及使用汇编、C和C++解决编程问题,1998年获得韩国信息奥林匹克竞赛铜牌。就读于韩国Yonsei大学计算机系期间,曾为多家公司编写高性能网络应用以及少量的Web程序,毕业后就职于Arreo通讯公司(为韩国最大的移动短信提供商之一)。Netty目前的项目负责人是德国人NormanMaurer(之前在Redhat,全职开发Netty),也是《NettyinAction》的作者,目前是苹果公司高级工程师。Netty框架组成JavaNIO提供了非阻塞IO开发模型,这样可以极大的提升服务端的并发处理性能,同时在NIO中又提供了基于零拷贝的数据处理操作,实现了高效的IO读写,而Netty的出现进一步简化了JavaNIO编程的难度,引入了更加丰富的线程模型设计、串行化处理读写(避免了锁带来的性能瓶颈)、内存池设计(实现内存重用),同时又支持Protobuf等高性能的序列化协议。正是因为Netty有着简单且性能高效的技术特点,所以很多的互联网公司都基于Netty开发出了各种即时通讯系统、RPC开发框架、消息组件以及大数据处理的应用,极大的丰富了Java开发的生态环境Netty框架组成丰富的零拷贝字节缓冲区(Zero-Copy-CapableRichByteBuffer)。零拷贝技术避免了系统进程和用户进程之间的数据传输,优化了IO通道的传输性能。Netty基于JavaNIO提供的Buffer机制进行了扩展,使其实现了更加多样化数据缓冲处理。需要注意的是Netty中的零拷贝指的是偏向于应用中的数据操作优化,不是简单的操作系统层面的优化。通用通信API(UniversalCommunicationAPI):Netty内部提供了丰富的线程模型、NIO封装处理,开发者直接通过框架提供的标准类库即可实现不同架构(BIO、NIO模型可以快速切换)的网络应用开发;扩展事件驱动(ExtensibleEventModel):在NIO已有的事件分类的基础上进行了事件的扩充,使Netty每个操作的处理更加规范;RTSP(Real-timeTransportProtocol、实时传输协议):可以为网络上的语音、图像、视频等多媒体文件提供端到端的实时数据传输服务;GoogleProtobuf(GoogleProtocolBuffers):支持谷歌提供的序列化工具支持,可以实现与开发语言无关、可扩展性强且传输高效的二进制数据;IDEA中创建Netty项目Netty是基于Java语言的技术包装,所以在使用之前需要开发者提供JDK的开发支持,考虑到后续代码维护的问题,本次的课程将基于JDK-17版本进行编写,使用IDEA开发工具,项目的构建以及依赖库的管理,将基于Gradle工具进行配置,下面来看下具体的项目搭建操作。pertiesorg.gradle.jvmargs=-Dfile.encoding=UTF-8pertiesproject_group=com.yootkproject_version=1.0.0project_jdk=17build.gradlegroupproject_group//组织名称

versionproject_version//项目版本

defenv=System.getProperty("env")?:'dev'//获取env环境属性

subprojects{//配置子项目

applyplugin:'java'//子模块插件

sourceCompatibility=project_jdk//源代码版本

targetCompatibility=project_jdk//生成类版本

repositories{//配置Gradle仓库

mavenLocal()//Maven本地仓库

maven{//阿里云仓库

allowInsecureProtocol=true

url'/nexus/content/groups/public/'}

maven{//spring官方仓库

allowInsecureProtocol=true

url'https://repo.spring.io/libs-milestone'

}

mavenCentral()//Maven远程仓库

}

dependencies{//公共依赖库管理

testImplementation(enforcedPlatform("org.junit:junit-bom:5.9.2"))

testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.2')

testImplementation('org.junit.vintage:junit-vintage-engine:5.9.2')

testImplementation('org.junit.jupiter:junit-jupiter-engine:5.9.2')

testImplementation('org.junit.platform:junit-platform-launcher:1.9.2')

implementation('org.slf4j:slf4j-api:2.0.6')//日志处理标准

implementation('ch.qos.logback:logback-classic:1.4.5')//Logback实现

}

sourceSets{//源代码目录配置

main{//main及相关子目录配置

java{srcDirs=['src/main/java']}

resources{srcDirs=['src/main/resources',"src/main/profiles/$env"]}

}

test{//测试目录配置

java{srcDirs=['src/test/java']}

resources{srcDirs=['src/test/resources']}

}

}

test{//配置测试任务

useJUnitPlatform()//使用JUnit测试平台

}

tasksourceJar(type:Jar,dependsOn:classes){//源代码打包任务

archiveClassifier='sources'//设置文件后缀

fromsourceSets.main.allSource//源代码读取路径

}

taskjavadocTask(type:Javadoc){//JavaDoc文档打包任务

options.encoding='UTF-8'//设置文件编码

source=sourceSets.main.allJava//所有Java源代码

}

taskjavadocJar(type:Jar,dependsOn:javadocTask){//先生成JavaDoc再打包

archiveClassifier='javadoc'//文件标记类型

fromjavadocTask.destinationDir//通过Javadoc任务找到目标路径

}

tasks.withType(Javadoc){//文档编码配置

options.encoding='UTF-8'//编码定义

}

tasks.withType(JavaCompile){//编译编码配置

options.encoding='UTF-8'//编码定义

}

artifacts{//最终打包操作任务

archivessourceJar//源代码打包

archivesjavadocJar//javadoc打包

}

gradle.taskGraph.whenReady{//在所有操作准备好后触发

tasks.each{task->//找出所有任务

if(.contains('test')){//如果发现test任务

task.enabled=true//执行测试任务

}

}

}

[compileJava,compileTestJava,javadoc]*.options*.encoding='UTF-8'//编码配置

}Netty项目结构ByteBuf继承结构为了减少用户进程与系统进程之间的数据拷贝操作,在JavaNIO中提供了Buffer的支持,但是在每次使用该缓冲区处理时都需要不断的通过flip()方法实现读写操作的切换,这样的操作方式在实际开发中就会显得比较繁琐,为此在Netty框架中,针对于此操作提供了一个ByteBuf缓冲数据类ByteBuf接口核心方法No.方法类型描述1publicabstractintcapacity()方法返回当前缓冲区的容量2publicabstractByteBufcapacity(intnewCapacity)方法为缓冲区重新分配新的容量3publicabstractintmaxCapacity()方法返回当前缓冲区允许保存的最大容量4publicabstractByteBufAllocatoralloc()方法返回创建缓冲区的ByteBufAllocator实例5publicabstractByteBufunwrap()方法返回包装的缓冲区实例,如未包装返回null6publicabstractbooleanisDirect()方法判断当前是否为NIO的直接缓冲区7publicabstractbooleanisReadOnly()方法判断当前是否为只读缓冲区8publicabstractintreaderIndex()方法返回缓冲区的读取索引9publicabstractByteBufreaderIndex(intreaderIndex)方法设置缓冲区的读取索引10publicabstractintwriterIndex();方法返回缓冲区的写入索引11publicabstractByteBufwriterIndex(intwriterIndex)方法设置缓冲区的写入索引12publicabstractByteBufsetIndex(intreaderIndex,intwriterIndex);方法设置缓冲区的读取及写入索引13publicabstractintreadableBytes()方法返回缓冲区中可以读取的字节数量14publicabstractintwritableBytes()方法返回缓冲区中可以写入的字节数量15publicabstractintmaxWritableBytes()方法返回缓冲区中最大可以写入的字节数量16publicabstractByteBufclear()方法清空缓冲区17publicabstractbooleangetBoolean(intindex)方法返回指定索引位置的布尔数据18publicabstractbytegetByte(intindex)方法返回指定索引位置的字节数据19publicabstractshortgetShort(intindex)方法返回指定索引位置的短整型数据(16位)20publicabstractintgetMedium(intindex);方法返回指定索引位置的整型数据(24位)21publicabstractintgetInt(intindex)方法返回指定索引位置的整型数据(32位)22publicabstractlonggetLong(intindex)方法返回指定索引位置的长整型数据(64位)23publicabstractchargetChar(intindex)方法返回指定索引位置的字符数据24publicabstractfloatgetFloat(intindex)方法返回指定索引位置的浮点型数据25publicabstractdoublegetDouble(intindex)方法返回指定索引位置的双精度浮点数据26publicabstractByteBufgetBytes(intindex,ByteBufdst,intdstIndex,intlength)方法缓冲区数据转换27publicabstractByteBufwriteByte(intvalue)方法向缓冲区中保存布尔数据28publicabstractByteBufwriteShort(intvalue)方法向缓冲区中保存短整型数据(16位)29publicabstractByteBufwriteMedium(intvalue)方法向缓冲区中保存整型数据(24位)30publicabstractByteBufwriteInt(intvalue)方法向缓冲区中保存整型数据(32位)31publicabstractByteBufwriteLong(longvalue)方法向缓冲区中保存长整型数据(64位)32publicabstractByteBufwriteChar(intvalue)方法向缓冲区中保存字符数据33publicabstractByteBufwriteFloat(floatvalue)方法向缓冲区中保存浮点型数据34publicabstractByteBufwriteDouble(doublevalue)方法向缓冲区中保存双精度浮点型数据35publicabstractByteBufwriteBytes(byte[]src)方法向缓冲区中设置一组字节数据38publicabstractByteBufreadBytes(byte[]dst)方法读取缓冲区内容到字节数组之中39publicabstractByteBufreadBytes(byte[]dst,intdstIndex,intlength)方法读取缓冲区指定范围的内容到字节数组之中41publicabstractByteBufcopy()方法缓冲区拷贝42publicabstractByteBufslice(intindex,intlength)方法缓冲区分配44publicabstractbyte[]array()方法获取缓冲区保存的字节数据45publicabstractByteBufdiscardReadBytes()方法释放废弃的缓冲区空间46publicabstractStringtoString(Charsetcharset)方法以字符串的形式获取缓冲区数据ByteBuf索引指针ByteBuf类的内部提供有Java全部数据类型的读写处理,同时也对已有的Java数据类型进行了扩充,这样的设计不仅可以简化程序中出现的数据转型操作,同时也可以减少数据传输的体积。对于缓存中的数据操作,采用了与JavaNIO类似的数据指针处理,其中每个指针的作用如下:数据读指针(readerIndex):定义了缓冲区中数据读取的开始索引,每读取一个字节后,该指针会加1;数据写入指针(writerIndex):定义了缓冲区中数据写入的索引,每次写入一个字节后,该指针会加1;缓冲区容量(capacity):表示缓冲区的容量,该容量为“废弃字节数量+可读字节数量+可写字节数量”;最大保存容量(maxCapacity):ByteBuf允许扩容存储,当存储数据的长度超过了capacity时,可以根据预设值的最大容量进行扩充,如果超过此容量会产生IndexOutOfBoundsException异常。base子模块project(":base"){

dependencies{

implementation('ty:netty-buffer:4.1.89.Final')

}

}创建ByteBuf缓冲区packagecom.yootk.test;

publicclassTestNettyBuffer{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestNettyBuffer.class);

publicstaticvoidmain(String[]args){

ByteBufbuf=ByteBufAllocator.DEFAULT.buffer(5,12);

LOGGER.info("【1】初始化:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

buf.writeBytes("java".getBytes());//数据写入

LOGGER.info("【2】数据写入:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

buf.writeBytes("-".getBytes());//数据写入

LOGGER.info("【3】数据写入:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

buf.writeBytes("netty".getBytes());//数据写入

LOGGER.info("【4】数据写入:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

}

}ByteBuf缓冲区指针操作获取缓冲区数据packagecom.yootk.test;

publicclassTestNettyBuffer{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestNettyBuffer.class);

publicstaticvoidmain(String[]args){

ByteBufbuf=ByteBufAllocator.DEFAULT.buffer(5,12);

LOGGER.info("【1】初始化:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

buf.writeBytes("java-netty".getBytes());//数据写入

buf.readerIndex(5);//修改读索引数据

LOGGER.info("【2】数据读取:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

buf.discardReadBytes();//释放废弃数据

LOGGER.info("【3】空间释放:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

byte[]data=newbyte[buf.capacity()];//开辟字节数组

intindex=0;//数组操作索引

while(buf.isReadable()){//缓冲区存在数据

data[index]=buf.readByte();//读取字节数据

index++;//修改索引

}

LOGGER.info("【4】缓冲区数据:{}",newString(data,0,index));

//LOGGER.info("【3】缓冲区数据:{}",buf.toString(CharsetUtil.UTF_8));//简化数据读取

}

}Netty缓冲区与JavaNIO缓冲区操作关联No.方法类型描述1publicabstractByteBufwriteBytes(ByteBuffersrc)方法向Netty缓冲区中写入一组NIO缓冲区数据2publicabstractintwriteBytes(FileChannelin,longposition,intlength)throwsIOException方法将FileChannel中的数据写入到缓冲区3publicabstractByteBufreadBytes(ByteBufferdst)方法读取数据到指定的NIO缓冲区之中4publicabstractByteBuffernioBuffer()方法以Netty缓冲区为基础,创建NIO缓冲区JavaNIO缓冲区与Netty缓冲区转换packagecom.yootk.test;

publicclassTestNettyBuffer{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestNettyBuffer.class);

publicstaticvoidmain(String[]args){

Stringcontent="沐言科技:";//待写入数据

ByteBufferbuffer=ByteBuffer.wrap(content.getBytes());//数据写入到NIO缓冲区

ByteBufbuf=ByteBufAllocator.DEFAULT.buffer(50);//创建Netty缓冲区

buf.writeBytes(buffer);//向Netty缓冲区写入数据

LOGGER.info(buf.toString(CharsetUtil.UTF_8));//输出Netty缓冲区数据

}

}结合Channel实现Netty缓冲区数据读写packagecom.yootk.test;

publicclassTestNettyBuffer{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestNettyBuffer.class);

publicstaticvoidmain(String[]args)throwsException{

Filefile=newFile("c:"+File.separator+"muyan-yootk.txt");//文件路径

RandomAccessFileraf=newRandomAccessFile(file,"rw");//随机读写

FileChannelchannel=raf.getChannel();//获取文件通道

byte[]data="沐言科技:".getBytes();//待写入数据

MappedByteBufferbuffer=channel.map(FileChannel.MapMode.READ_WRITE,0,data.length);//内存映射

buffer.put(data);//数据写入

ByteBufbuf=ByteBufAllocator.DEFAULT.buffer(50);//创建Netty缓冲区

buf.writeBytes(channel,0,data.length);//数据写入到指定通道

channel.close();//关闭通道

LOGGER.info("Netty缓冲区数据:{}",buf.toString(CharsetUtil.UTF_8));

}

}ByteBufUtil类结构ByteBufUtil类是Netty提供的一个字节缓冲区的数据处理工具类,为了便于数据处理的方便,该类提供有如图所示的四个内部类,分别可以实现字节数据检索、十六进制数据处理以及直接内存缓冲区的开辟。ByteBufUtil类常用方法No.方法类型描述1publicstaticbooleanisAccessible(ByteBufbuffer)方法判断字节缓冲区是否可以访问2publicstaticByteBufensureAccessible(ByteBufbuffer)方法确认缓冲区是否可以使用3publicstaticStringhexDump(ByteBufbuffer)方法缓冲区转为十六进制字符串4publicstaticbyte[]decodeHexDump(CharSequencehexDump)方法将hexDump()结果转换为字节数组5publicstaticinthashCode(ByteBufbuffer)方法获取字节缓冲区哈希码6publicstaticbooleanequals(ByteBufbufferA,ByteBufbufferB)方法判断两个字节缓冲区是否相等7publicstaticintcompare(ByteBufbufferA,ByteBufbufferB)方法比较两个字节缓冲区的内容大小8publicstaticintwriteUtf8(ByteBufbuf,CharSequenceseq)方法将字符串写入字节缓冲区之中(UTF8编码)9publicstaticintwriteAscii(ByteBufbuf,CharSequenceseq)方法将字符串写入字节缓冲区之中(ASCII编码)10publicstaticbyte[]getBytes(ByteBufbuf)方法返回字节缓冲区中的字节数据11publicstaticByteBufthreadLocalDirectBuffer()方法根据环境返回缓存线程外部缓存空间12publicstaticintindexOf(ByteBufbuffer,intfromIndex,inttoIndex,bytevalue)方法判断字节缓冲区指定范围内是否包含有指定的字节数据十六进制转换packagecom.yootk.test;

publicclassTestByteBufUtil{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestByteBufUtil.class);

publicstaticvoidmain(String[]args){

StringhexDump=ByteBufUtil.hexDump("java-netty".getBytes());//字节数组转十六进制字符串

LOGGER.info("十六进制字符串:{}",hexDump);

bytedata[]=ByteBufUtil.decodeHexDump(hexDump);//十六进制字符串转字节数组

LOGGER.info("十六进制转换:{}",newString(data));

}

}比较两个字节缓冲区中的数据是否相同packagecom.yootk.test;

publicclassTestByteBufUtil{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestByteBufUtil.class);

publicstaticvoidmain(String[]args){

ByteBufbufA=ByteBufAllocator.DEFAULT.buffer(50,100);

ByteBufUtil.writeUtf8(bufA,"沐言科技:");

ByteBufbufB=ByteBufAllocator.DEFAULT.buffer(50,100);

ByteBufUtil.writeUtf8(bufB,"沐言科技:");

LOGGER.info("缓冲区比较:{}",ByteBufUtil.equals(bufA,bufB));

}

}创建直接缓存以减少堆内存空间的占用packagecom.yootk.test;

publicclassTestByteBufUtil{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestByteBufUtil.class);

publicstaticvoidmain(String[]args){

ByteBufbuf=ByteBufUtil.threadLocalDirectBuffer();//需要配置JVM参数

if(ByteBufUtil.isAccessible(buf)){//缓存可以访问

LOGGER.info("【1】初始化:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

ByteBufUtil.writeUtf8(buf,"沐言科技:");//数据写入

LOGGER.info("【2】数据写入:readerIndex={}、writerIndex={}、capacity={}、maxCapacity={}",

buf.readerIndex(),buf.writerIndex(),buf.capacity(),buf.maxCapacity());

LOGGER.info("【3】缓冲数据:{}",buf.toString(StandardCharsets.UTF_8));

}

}

}PlatformDependent在进行直接内存分配操作中,Java提供了Unsafe与DirectByteBuffer两种处理模式,如图所示,在默认情况下,用户通过ByteBufUtil.threadLocalDirectBuffer()方法获取到的是ThreadLocalUnsafeDirectByteBuf对象实例,即由Unsafe类分配的直接内存,而这一操作是由PlatformDependent类中提供的方法来控制的。PlatformDependent类常用方法No.方法类型描述1publiclongbyteArrayBaseOffset()方法返回字节数组的偏移量2publicstaticbyte[]allocateUninitializedArray(intsize)方法分配未初始化的数组3publicstaticbooleanisAndroid()方法是否为Android平台4publicstaticbooleanisWindows()方法是否为Windows平台5publicstaticbooleanisOsx()方法是否为MacOS平台6publicstaticbooleanisIkvmDotNet()方法是否为IKVM虚拟机平台7publicstaticintjavaVersion()方法获取Java开发版本8publicstaticlongmaxDirectMemory()方法返回为直接缓冲区保留的最大内存9publicstaticlongusedDirectMemory()方法返回为直接缓冲区保留的当前内存10publicstaticFiletmpdir()方法获取临时目录路径11publicstaticintbitMode()方法返回当前虚拟机的位数12publicstaticintaddressSize()方法返回操作系统的地址大小13publicstaticlongallocateMemory(longsize)方法分配指定长度的直接内存14publicstaticvoidfreeMemory(longaddress)方法释放分配的直接内存15publicstaticlongreallocateMemory(longaddress,longnewSize)方法重新分配直接内存16publicstaticvoidfreeDirectBuffer(ByteBufferbuffer)方法释放直接内存缓冲区17publicstaticlongdirectBufferAddress(ByteBufferbuffer)方法返回直接内存缓冲区的地址18publicstaticByteBufferdirectBuffer(longmemoryAddress,intsize)方法创建直接缓冲区19publicstaticObjectgetObject(Objectobject,longfieldOffset)方法获取对象数据20publicstaticintgetInt(Objectobject,longfieldOffset)方法获取整型数据21publicstaticbytegetByte(longaddress)方法获取字节数据22publicstaticshortgetShort(longaddress)方法获取短整型数据23publicstaticintgetInt(longaddress)方法获取整型数据24publicstaticlonggetLong(longaddress)方法获取长整型数据25publicstaticvoidputByte(longaddress,bytevalue)方法保存字节数据26publicstaticvoidputShort(longaddress,shortvalue)方法保存短整型数据27publicstaticvoidputInt(longaddress,intvalue)方法保存整型数据28publicstaticvoidputLong(longaddress,longvalue)方法保存长整型数据29publicstaticvoidputByte(byte[]data,intindex,bytevalue)方法保存字节数组30publicstaticvoidputByte(Objectdata,longoffset,bytevalue)方法保存字节数据31publicstaticvoidcopyMemory(longsrcAddr,longdstAddr,longlen)方法内存拷贝32publicstaticbooleanisZero(byte[]bytes,intstartPos,intlength)方法数据指定字节中是否有内容为033publicstaticClassLoadergetContextClassLoader()方法获取应用上下文类加载器34publicstaticClassLoadergetSystemClassLoader()方法获取系统类加载器35publicstaticRandomthreadLocalRandom()方法返回线程安全的随机数对象实例36publicstaticlongestimateMaxDirectMemory()方法估算JVM可用的最大直接内存量37publicstatic<K,V>ConcurrentMap<K,V>newConcurrentHashMap(intinitialCapacity)方法创建并发Map集合判断当前应用运行环境packagecom.yootk.test;

publicclassTestPlatformDependent{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestPlatformDependent.class);

publicstaticvoidmain(String[]args){

LOGGER.info("【1】当前是否为Android系统:{}",PlatformDependent.isAndroid());

LOGGER.info("【2】当前是否为Windows系统:{}",PlatformDependent.isWindows());

LOGGER.info("【3】当前是否为MacOS系统:{}",PlatformDependent.isOsx());

LOGGER.info("【4】当前是否为IKVM系统:{}",PlatformDependent.isIkvmDotNet());

}

}Unsafe实现直接内存的分配以及读写处理packagecom.yootk.test;

publicclassTestPlatformDependent{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestPlatformDependent.class);

privatestaticintBIT_LENGTH=8;//写入位数

publicstaticvoidmain(String[]args){

LOGGER.info("可分配的最大直接内存量:{}",PlatformDependent.maxDirectMemory());

longaddress=PlatformDependent.allocateMemory(200);//分配直接缓存

LOGGER.info("分配直接缓存,内存地址为:{}",address);

byte[]data="沐言科技:&".getBytes();//定义要写入数据

for(intx=0;x<data.length;x++){//循环写入

PlatformDependent.putByte(address+BIT_LENGTH*x,data[x]);//数据写入

}

byte[]result=newbyte[50];//接收数据

for(intx=0;x<result.length;x++){

bytetemp=PlatformDependent.getByte(address+BIT_LENGTH*x);//读取数据

if(temp=='&'){//结束符判断

break;

}

result[x]=temp;//保存字节数据

}

System.out.println(newString(result).trim());//字节数组转为字符串输出

PlatformDependent.freeMemory(address);//手工释放内存

}

}PlatformDependent实现内存分配分配直接内存packagecom.yootk.test;

publicclassTestByteBufUtil{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(TestByteBufUtil.class);

publicstaticvoidmain(String[]args){

ByteBufbuf=ByteBufUtil.threadLocalDirectBuffer();//需要配置JVM参数

LOGGER.info("【类型】ByteBuf对象实例:{}",buf.getClass().getName());

}

}配置JVM参数:-Dty.threadLocalDirectBufferSize=1-Dty.noUnsafe=true程序执行结果:【类型】ByteBuf对象实例:ty.buffer.ByteBufUtil$ThreadLocalDirectByteBufThreadLocal存储ThreadLocal是Java在进行多线程并发操作中较为常用的一个类,但是在使用ThreadLocal进行数据存储时,会基于数据计算出一个哈希值,随后才能够将数据实体保存在相应的位置之中,但是在进行存储的过程中,由于会存在有哈希冲突的可能性,这样一来就需要重复进行哈希计算,在并发量较大的情况下会直接影响到应用的性能。FastThreadLocal存储Netty中为了解决传统ThreadLocal性能的设计问题,提供了一个FastThreadLocal改进结构。在使用FastThreadLocal进行数据保存时,不是基于哈希计算的保存位置,而是为每一个FastThreadLocal分配一个不重复的index下标(通过AtomicInteger保存,解决了多线程并发操作下的数据同步问题),在进行存储时按照数组下标的方式进行数据保存,这样不仅解决了哈希冲突的影响,同时在获取数据时其操作复杂度也降至“O(1)”。FastThreadLocal类关联结构Netty设计FastThreadLocal存储结构时,考虑到了原始Thread数据存储的要求,所以在定义InternalThreadLocalMap集合时,分别采用了快速检索(FastThreadLocal)以及慢速检索(ThreadLocal)两种存储机制,如果发现当前操作的线程类型为FastThreadLocal,则使用快速检索获取数据,反之如果为ThreadLocal类型则使用慢速检索使用FastThreadLocal实现多线程数据存储与获取packagecom.yootk.test;

classMessageStore{

privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(MessageStore.class);

//创建FastThreadLocal对象实例,该对象只允许保存FastThreadLocalThread线程对象

privatestaticfinalFastThreadLocal<String>FAST=newFastThreadLocal<>();

privateMessageStore(){}

publicstaticvoidsave(Stringmessage){//向集合中保存数据

LOGGER.info("【数据设置】线程名称:{}、数据内容:{}",Thread.currentThread().getName(),message);

FAST.set(message);//数据保存

}

publicstaticStringload(){//通过集合获取数据

Stringvalue=FAST.get();

LOGGER.info("【数据加载】线程名称:{}、数据内容:{}",Thread.currentThread().getName(),value);

returnvalue;//数据读取

}

}

publicclassTestFastThreadLocal{

publicstaticvoidmain(String[]args)throwsException{

CountDownLatchcountDownLatch=newCountDownLatch(3);//线程同步

Stringdata[]=newString[]{"《Java程序设计开发实战》","《SSM开发实战》",

"《SpringBoot开发实战》"};//待写入数据

for(intx=0;x<3;x++){//循环创建三个线程

inttemp=x;//便于Lambda内部操作

newFastThreadLocalThread(()->{

MessageStore.save(data[temp]);//数据设置

try{

TimeUnit.SECONDS.sleep(1);//强制性延迟

}catch(InterruptedExceptione){}

MessageStore.load();//数据读取

countDownLatch.countDown();//计数减1

},"Fast线程-"+x).start();

}

countDownLatch.await();//线程同步

}

}ByteBufAllocator接口Netty进行缓冲区分配时,可以根据内存分配位置的不同分为堆内存(Heap)缓冲区和直接内存(Direct)缓冲区,虽然使用ByteBuf提高了用户态与系统态进程之间的数据传输性能,但是在高并发访问下,缓冲区的分配依然属于耗时的操作,一旦处理不当则一定会产生严重的性能问题。ByteBuf

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论