当前位置: 首页 > news >正文

临沂做网站费用家装博览会

临沂做网站费用,家装博览会,系统工具,河北邢台企业做网站文章目录 一、粘包和拆包1、半包问题2、半包现象原理 二、JSON协议通信1、通用类库2、JSON传输的编码器和解码器 三、Protobuf协议通信1、一个简单的proto文件的实践案例2、生成POJO和Builder3、消息POJO和Builder的使用案例1#xff09;构造POJO消息对象2#xff09;序列化和… 文章目录 一、粘包和拆包1、半包问题2、半包现象原理 二、JSON协议通信1、通用类库2、JSON传输的编码器和解码器 三、Protobuf协议通信1、一个简单的proto文件的实践案例2、生成POJO和Builder3、消息POJO和Builder的使用案例1构造POJO消息对象2序列化和反序列化 四、Protobuf编解码的实践案例1、Protobuf编码器和解码器的原理2、示例 五、Protobuf协议语法1、头部声明2、消息结构体3、其他的语法规范 因为像TCP和UDP这种底层协议只能发送字节流因此当我们在开发一些远程过程调用RPC的程序时需要将应用层的Java POJO对象序列化成字节流数据接收端再反序列化成Java POJO对象。序列化一定会设计编码和格式化目前常见的编码方式有 JSON将Java POJO对象转换成JSON结构化字符串。基于HTTP协议是常用的编码方式可读性较强性能稍差XML和JSON一样数据在序列化成字节流之前都转换成字符串可读性强但是性能差Java内置的编码和序列化机制可移植性强性能稍差无法跨平台语言其他开源的序列化/反序列化机制例如Apache AvroApache Thrift这两个框架和Protobuf相比性能非常接近且设计原理如出一辙。其中Avro在大数据存储时比较常用Thrift的亮点在于内置了RPC机制所以在开发一些RPC交互式应用时客户端和服务端的开发和部署都非常简单 评价一个序列化框架的优缺点 结果数据大小序列化后的数据越小传输效率越高结构复杂度这会影响序列化/反序列化的效率结构越复杂越耗时 理论上对于性能要求不是太高的服务器程序可以选择JSON系列的序列化框架而对于性能要求较高的服务器程序则应该选择传输效率更高的二进制序列化框架如Protobuf。 一、粘包和拆包 每一次发送就是向通道写入一个ByteBuf。发送数据时先填好ByteBuf然后通过通道发出去。对于接收端每一次读取就是通过Handler业务处理器的入站方法从通道读到一个ByteBuf。最理想的情况就是发送端每发送一个ByteBuf缓冲区接收端就能接收到一个ByteBuf并且发送端和接收端的ByteBuf内容能一模一样。 1、半包问题 然而事实是接收方收到的数据包并不总按我们的预期而是可能存在三种情况 全包读到一个完整的ByteBuf粘包读到多个ByteBuf输入粘在了一起半包只读到部分ByteBuf内容并且有乱码 粘包就是接收端收到一个ByteBuf但是包含了多个发送端的ByteBuf即多个ByteBuf粘在了一起。半包就是接收端将一个发送端的ByteBuf拆开了收到了多个破碎的包。为了简单起见也可以将粘包的情况看成特殊的半包粘包和半包可以统称为传输的半包问题都是指一次不正常的ByteBuf缓存区接收。 2、半包现象原理 寻根粘包和半包的来源得从操作系统底层说起。底层网络都是以二进制字节报文的形式来传输数据的读数据的大致流程为当IO可读时Netty会从底层网络将二进制数据读到ByteBuf缓冲区中再交给Netty程序转换成Java POJO对象。 在发送端Netty的应用层进程缓冲区程序以ByteBuf为单位来发送数据但是到了底层操作系统的内核缓冲区 底层会按照协议的规范对数据包进行二次封装拼成传输层TCP协议报文再进行发送。在接收端收到传输层的二进制包后首先保存在内核缓冲区Netty读取ByteBuf时才复制到进程缓冲区。 在接收端当Netty程序将数据从内核缓冲区复制到Netty进程缓冲区的ByteBuf时问题就来了 首先每次读取底层缓冲的数据容量是有限制的当TCP底层缓冲的数据包比较大时会将一个底层包分成多个ByteBuf进行赋值进而造成进程缓冲区读到的是半包当TCP底层缓冲的数据包比较小时一次复制的却不止一个内核缓冲区包进而造成进程缓冲区读取到的是粘包 那么解决的基本思路就是在接收端Netty程序根据自定义协议将读取到的进程缓冲区ByteBuf在应用层进行二次拼装重新组装我们应用层的数据包。接收端的这个过程通常也称为分包或者叫做拆包。 在Netty中分包的方法主要有两种 自定义解码器分包器基于ByteToMessageDecoder或ReplayingDecoder定义自己的进程缓冲区分包器使用Netty内置的解码器如DelimiterBasedFrameDecoder或LengthFieldBasedFrameDecoder等解码器 二、JSON协议通信 1、通用类库 Java处理JSON数据有三个比较流行的开源类库 阿里的FastJson谷歌的Gson开源社区的Jackson Jackson是一个简单的、基于JavadeJSON开源库可以轻松地将Java POJO对象转换成JSON、XML格式字符串同样也可以方便地将JSON、XML字符串转换成Java POJO对象。它的优点是依赖的jar包较少简单易用性能也还不错。但是缺点是对于复杂的Pojo类型、复杂的集合Map、List的转换结果不是标准的JSON格式或者会出现一些问题。 Google的Gson开源库是一个功能齐全的JSON解析库可以完成复杂类型的POJO和JSON字符串的相互转换转换能力非常的强。 阿里巴巴的FastJson是一个高性能的JSON库采用独创的算法将JSON转换成POJO的速度提升到极致超过其他JSON开源库。 在实际开发中目前主流的策略是使用Gson将POJO序列化成JSON字符串用FastJson将JSON字符串反序列化成POJO对象。下面是使用这两个类库封装出来的Json通用工具类。 public class JsonUtil {// 谷歌的GsonBuilder构造器static GsonBuilder gb new GsonBuilder();static {gb.disableHtmlEscaping();}public static String pojoToJson(Object obj) {return gb.create().toJson(obj);}public static T T jsonToPojo(String json, ClassT tClass) {return JSONObject.parseObject(json, tClass);} }2、JSON传输的编码器和解码器 本质上JSON格式仅仅是字符串的一种组织形式所以传输JSON所用到的协议和传输普通文本所使用的协议没什么不同。下面使用采用的Head-Content协议来介绍一下JSON传输。 解码过程先使用LengthFieldBasedFrameDecoderNetty内置的自定义长度数据包解码器解码Head-Content二进制数据包解码出Content字段的二进制内容。然后使用StringDecoder字符串解码器Netty内置的解码器将二进制内容解码成JSON字符串。最后使用JsonMsgDecoder自定义的解码器将JSON字符串解码成POJO对象。 编码过程先使用StringEncoder编码器Netty内置将JSON字符串编码成二进制字节数组然后使用LengthFieldPrepender编码器Netty内置将二进制字节数组编码成Head-Content二进制数据包。 LengthFieldPrepender编码器的作用是在数据包的前面加上内容的二进制字节数组的长度和LengthFieldBasedFrameDecoder解码器是配对使用的。构造器需要传入两个参数 int lengthFieldLength表示Head长度字段所占用的字节数boolean lengthIncludesLengthFieldLength表示Head字段的总长度值是否包含长度字段自身的字节数默认为false 三、Protobuf协议通信 Protobuf是Google提出的一种数据交换的格式是一套类似JSON或者XML的数据传输格式和规范用于不同应用或进程之间进行通信。Protobuf的编码过程为使用预先定义的Message数据结构的传输数据进行打包然后编码成二进制的码流进行传输或存储。Protobuf的解码过程则是将二进制码流解码成Protobuf自己定义的Message结构的POJO实例。 Protobuf既独立于语言又独立于平台。Google官方提供了多种语言的实现Java、C#、C、Go、JavaScript和Python。Protobuf数据包是一种二进制的格式相对于文本格式的数据交换JSON、XML来说速度要快很多。由于Protobuf优异的性能使得它更加适用于分布式应用场景下的数据通信或异构环境下的数据交换。 JSON和XML是文本格式数据具有可读性而Protobuf是二进制数据格式数据本身不具有可读性只有反序列化之后才能得到真正可读的数据。正因为Protobuf是二进制数据格式数据序列化之后体积相比JSON和XML药效更加适合网络传输。 总的来说在一个需要大量数据传输的应用场景因为数据量很大那么选择Protobuf可以明显地减少传输的数据量和提升网络IO的速度。对于打造一款高性能的通信服务器来说Protobuf传输协议是最高性能的传输协议之一微信的消息传输就采用了Protobuf协议。 1、一个简单的proto文件的实践案例 Protobuf使用proto文件来预先定义的消息格式。数据包是按照proto文件所定义的消息格式完成二进制码流的编码和解码。proto文件简单来说就是一个消息的协议文件这个协议文件的后缀文件名为“.proto”。如下为简单的示例 // [开始头部声明] syntax proto3; package cn.ken.netty.protocol; // [结束头部声明]// [开始java选项配置] option java_package cn.ken.netty.protocol; option java_outer_classname MsgProtos; // [结束java选项配置]// [开始消息定义] message Msg {uint32 id 1; // 消息IDstring content 2; // 消息内容 } // [结束消息定义]在“.proto”文件的头部声明中需要声明“.proto”所使用的Protobuf协议版本默认的协议版本为“proto2” Protobuf支持很多语言所以它为不同的语言提供了一些可选的声明选项选项的前面有option关键字 “java_package选项的作用为在生成“proto”文件中消息的POJO类和Builder构造者的Java代码时将Java代码放入指定的package中“java_outer_classname”选项的作用为在生成“proto”文件所对应的Java代码时所生产的Java外部类的名称 在“proto”文件中使用message这个关键字来定义消息的结构体。在生成“proto”对应的Java代码时每个具体的消息结构体都对应于一个最终的Java POJO类。消息结构提的字段对应到POJO类的属性。message中可以内嵌message就像Java的内部类一样 每一个消息结构体可以有多个字段。定义一个字段的格式简单来说就是“类型名称编号”。字段序号表示为在Protobuf数据包的序列化、反序列化时该字段的具体排序 在每一个“.proto”文件中可以声明多个“message”。大部分情况下会把有依赖关系或者包含关系的message消息结构体写入一个.proto文件。将那些没有关联关系的message消息结构体分别写入不同的文件这样便于管理。 2、生成POJO和Builder 完成“.proto”文件定义后下一步就是生成消息的POJO类和Builder类。有两种方式生成Java类一种是通过控制台命令的方式一种是使用Maven插件的方式。 控制台生成protoc.exe --java_out./src/main/java/ ./Msg.proto该命令表示“proto”文件的名称为./Msg.proto所生产的POJO类和构造者类的输出文件为./src/main/java/ maven生成protobuf-maven-plugin插件 plugingroupIdorg.xolstice.maven.plugins/groupIdartifactIdprotobuf-maven-plugin/artifactIdversion0.5.0/versionextensionstrue/extensionsconfiguration!--proto文件路径--protoSourceRoot${project.basedir}/protobuf/protoSourceRoot!--目标路径--outputDirectory${project.build.sourceDirectory}/outputDirectory!--设置是否在生成Java文件之前情况outputDirectory的文件--clearOutputDirectoryfalse/clearOutputDirectory!--临时目录--temporaryProtoFileDirectory${project.build.directory}/protoc-temp/temporaryProtoFileDirectory!--protoc可执行文件路径--protocExecutable${project.basedir}/protobuf/protoc3.6.1.exe/protocExecutable/configurationexecutionsexecutiongoalsgoalcompile/goalgoaltest-compile/goal/goals/execution/executions /plugins3、消息POJO和Builder的使用案例 1构造POJO消息对象 public static MsgProtos.Msg buildMsg() {MsgProtos.Msg.Builder builder MsgProtos.Msg.newBuilder();builder.setId(100).setContent(hello);return builder.build(); }protobuf为每个message消息结构体生成的Java类中包含了一个POJO类一个Builder类。构造POJO消息首先需要使用POJO类的newBuilder静态方法获得一个Builder构造者。。每一个POJO字段的值需要通过Builder的setter方法去设置消息POJO对象并没有setter方法。字段值设置完成之后哦使用构造者的build方法构造出POJO消息对象。 2序列化和反序列化 public class ProtoTest {Testpublic void test1() throws IOException {MsgProtos.Msg msg buildMsg();// 将protobuf对象序列化为二进制字节数组byte[] bytes msg.toByteArray();ByteArrayOutputStream stream new ByteArrayOutputStream();stream.write(bytes);// 反序列化MsgProtos.Msg inMsg MsgProtos.Msg.parseFrom(stream.toByteArray());System.out.println(inMsg.getId());System.out.println(inMsg.getContent());}Testpublic void test2() throws IOException {MsgProtos.Msg msg buildMsg();// 将protobuf对象序列化为二进制字节数组byte[] bytes msg.toByteArray();ByteArrayOutputStream stream new ByteArrayOutputStream();msg.writeTo(stream);// 反序列化MsgProtos.Msg inMsg MsgProtos.Msg.parseFrom(stream.toByteArray());System.out.println(inMsg.getId());System.out.println(inMsg.getContent());}Testpublic void test3() throws IOException {MsgProtos.Msg msg buildMsg();// 将protobuf对象序列化为二进制字节数组byte[] bytes msg.toByteArray();ByteArrayOutputStream stream new ByteArrayOutputStream();msg.writeDelimitedTo(stream);// 反序列化ByteArrayInputStream inputStream new ByteArrayInputStream(bytes);MsgProtos.Msg inMsg MsgProtos.Msg.parseDelimitedFrom(inputStream); System.out.println(inMsg.getId());System.out.println(inMsg.getContent());} } 方法三类似于Head-Content协议在序列化的字节码之前添加了字节数组的长度反序列化时protubuf从输入流中先读取varint32类型的长度值然后根据长度值读取此消息的二进制字节在反序列化得到POJO新的实例。 Protobuf做了优化长度类型不是固定长度的int类型而是可变长度varint32类型 这种方式可以用于异步操作的NIO应用场景中解决了粘包/半包问题。 四、Protobuf编解码的实践案例 Netty默认支持Protobuf的编码和解码内置了一套基础的Protobuf编码和解码器。 1、Protobuf编码器和解码器的原理 Netty内置的Protobuf专用的基础编码器/解码器为ProtobufEncoder编码器和ProtobufDecoder解码器。 ProtobufEncoder编码器直接使用了message.toByteArray()方法将Protobuf的POJO消息对象编码成二进制字节数据放入Netty的Bytebuf数据包中然后交给下一个编码器ProtobufDecoder解码器该类的构造函数需要传入一个POJO消息的对象实例以此来将二进制的字节解析为Protobuf POJO消息对象ProtobufVarint32LengthFieldPrepender长度编码器在ProtobufEncoder生成的字节数组之前前置一个varint32数字表示序列化的二进制字节数ProtobufVarint32FrameDecoder长度解码器根据数据包中varint32中的长度值解码一个足额的字节数组然后将字节数组交给下一站的解码器ProtobufDecoder 什么是varint32类型的长度为什么不用int类型 varint32是一种紧凑地表示数字的方式它不是一种具体的数据类型。varint32使用一个或多个字节来表示一个数字值越小的数字使用越少的字节数值越大使用的字节数越多。varint32根据值的大小自动进行长度的收缩这能减少用于保存长度的字节数。也就是说varint32不是固定长度为了更好地减少通信过程中的传输量消息头中的长度尽量采用varint32格式。 2、示例 服务端 public class ProtobufServer {public void runServer() throws InterruptedException {ServerBootstrap bootstrap new ServerBootstrap();NioEventLoopGroup bossLoopGroup new NioEventLoopGroup(1);NioEventLoopGroup workerLoopGroup new NioEventLoopGroup(0);// 1.设置反应器线程组bootstrap.group(bossLoopGroup, workerLoopGroup);// 2.设置nio类型的通道bootstrap.channel(NioServerSocketChannel.class);// 3.设置监听端口bootstrap.localAddress(8000);// 4.设置通道的参数bootstrap.option(ChannelOption.SO_KEEPALIVE, true);bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);bootstrap.childHandler(new ChannelInitializerSocketChannel() {Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()).addLast(new ProtobufDecoder(MsgProtos.Msg.getDefaultInstance())).addLast(new ProtobufBusinessDecoder());}});ChannelFuture bind bootstrap.bind().sync();bind.channel().closeFuture().sync();bossLoopGroup.shutdownGracefully();workerLoopGroup.shutdownGracefully();}static class ProtobufBusinessDecoder extends ChannelInboundHandlerAdapter {Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {MsgProtos.Msg protoMsg (MsgProtos.Msg) msg;System.out.println(protoMsg.getId());System.out.println(protoMsg.getContent());}}public static void main(String[] args) throws InterruptedException {new ProtobufServer().runServer();} } 客户端 public class ProtobufClient {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap new Bootstrap();NioEventLoopGroup workerLoopGroup new NioEventLoopGroup();bootstrap.group(workerLoopGroup);bootstrap.channel(NioSocketChannel.class);bootstrap.remoteAddress(new InetSocketAddress(localhost, 8000));bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);bootstrap.handler(new ChannelInitializerSocketChannel() {Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()).addLast(new ProtobufEncoder());}});ChannelFuture connect bootstrap.connect();connect.sync();Channel channel connect.channel();for (int i 0; i 1000; i) {MsgProtos.Msg user MsgProtos.Msg.newBuilder().setId(i).setContent(hello i).build();channel.writeAndFlush(user).sync();}workerLoopGroup.shutdownGracefully();} } 五、Protobuf协议语法 在Protobuf中通信协议的格式是通过“.proto”文件定义的。一个“.proto”文件有两大组成部分头部声明、消息结构体的定义。 1、头部声明 协议的版本syntax 包名package用于避免信息名字冲突 特定语言的选项设置option option java_package表示Protobuf编译器在生成Java POJO消息类时生成类所在的Java包名如果没有设置该选项会以头部声明中的package作为Java包名 option java_multiple_files表示在生成Java类时的打包方式有两种方式 一个消息对应一个独立的Java类所有的消息都作为内部类打包到一个外部类中默认值为false也就是方法2 2、消息结构体 可以定义一个或多个消息结构体。定义Protobuf消息结构体的关键字为message。一个信息结构体由一个或者多个消息字段组合而成。 Protobuf消息字段的格式为 限定修饰符 repeated表示该字段可以包含0-N个元素值相当于Java中的List singular表示该字段可以包含0-1个元素值是默认的字段修饰符 reserved用来保留字段名称和分配标识符号用于将来的扩展 reserved 12, 5, 9 to 11; // 预留将来使用的分配标识号reserved “foo”, “bar”; // 预留将来使用的字段名 数据类型 doubleFloatint32使用变长编码对于负值的效率很低如果字段有可能有负值使用sint64代替uint32使用变长编码uint64使用变长编码sint32使用变长编码在负值时比int32高效得多sint64使用变长编码有符号的整型值编码时比通常的int64高效fixed32固定4个字节如果数值总是大于228这个类型会比uint32高效fixed64固定8个字节如果数值总是大于256这个类型会比uint64高效sfixed32sfixed64BoolStringBytes 字段名称建议采用下划线分割 分配标识号在消息定义中每个字段都有唯一的一个数字标识符可以理解为字段的编码值。通过该值通信双方才能互相识别对方的字段。相同的编码值的限定修饰符和数据类型必须相同。 分配标识号是用来在消息的二进制格式中识别各个字段的一旦开始使用就不能再改变一个消息结构体中的标识号是无需连续的同一个消息结构体中不同的字段不能使用相同的标识号取值范围为4个字节的整数且1900-2000之内的标识号为Google Protobuf系统的内部保留值建议不要在自己的项目中使用 fixed32的打包效率比int32的效率高但是使用的空间一般比int32多。根据项目的实际情况一般选择fixed32如果遇到对于传输数据量要求比较苛刻的环境则可以选择int32。如果数值较小如在0-127时其只需要使用一个字节打包。 3、其他的语法规范 import声明在需要多个消息结构体时“.proto”文件可以像Java语言的类文件一样分离为多个在需要时通过import导入需要的文件。导入的操作和Java的import操作大致相同 嵌套消息“.protp”支持嵌套消息消息中可以包含另一个消息作为其字段也可以在消息中定义一个新的消息。如果想在父消息类型的外部重复使用内部的消息类型可以使用Parent.Type的形式来使用。如 message Outer{message Middle{message Inner{int64 ival 1;bool booly 2;}} } message SomeOtherMessage{Outer.Middle.Inner ref 1; }enum枚举枚举的定义和Java相同但是有一些限制。枚举值必须大于等于0的整数。使用分号分割枚举变量而不是Java中的逗号
http://www.yingshimen.cn/news/43108/

相关文章:

  • 人力招聘网站建设任务执行书广州注册公司必看
  • 益阳市网站建设科技遵义建设厅官方网站
  • seo网站优化推荐宾馆会员卡管理系统
  • wordpress 电影站主题图片上加语音 网站开发
  • 公司网站做么做百度排名oa管理系统免费版
  • 家具玻璃镜定做东莞网站建设第三方电子商务平台有哪些
  • 东莞在线网站制作平台微分销平台登录
  • 个人官方网站怎么建设网站侵权怎么做公证或证据保存
  • 做公众号的网站模板cms建站
  • 网站开发资格证书南昌专业制作网站设计
  • 企业网站建设有什么要求西安网站建设专业公司
  • 怎么样百度搜到自己的网站wordpress彻底禁用google
  • 无刷新网站怎么用自己的电脑搭建网站
  • 定制开发网站黑龙江建设厅网站 孙宇
  • 获取网站验证码地址pptppt模板免费下载
  • 宁夏商擎网站建设网站建设准备资料
  • 家装网站建设宁波网站制作优化服务
  • 网站建设中网站需求分析做网上招聘哪个网站好
  • 网站建设wuliankj长沙百度推广公司
  • WordPress添加图片模块seo全称是什么
  • 建设财经资讯网站的目的网站导航怎么做外链
  • 旅游网站建设合同做网站设计怎么提升
  • 河南那家做网站实力强无代码开发软件
  • 网站开发 定制 合同 模板h5做招聘网站可以吗
  • 为什么做视频网站违法wordpress建站产品导入不同目录
  • 中建八局土木建设有限公司网站学做ppt的网站有哪些内容
  • 公司网站建设调研问卷网站首页页面设计多少钱
  • 无锡网络公司平台晨阳seo
  • 建设景区网站要有的内容2019年做网站还有前景吗
  • 南昌建设局网站wordpress 摘要字数