netty 默认支持protobuf 的封装与解码,如果通信双方都使用netty则没有什么障碍,但如果客户端是其它语言(C#)则需要自己仿写与netty一致的方式(解码+封装),提前是必须很了解netty是如何进行封装与解码的。这里主要通过读源码主要类ProtobufVarint32FrameDecoder(解码)+ProtobufVarint32LengthFieldPrepender(封装) 来解析其原理与实现。

文章来源http://www.cnblogs.com/tankaixiong

一,支持protobuf 协议的默认实现

//配置服务端NIO线程组  EventLoopGroup bossGroup = new NioEventLoopGroup();  EventLoopGroup workerGroup = new NioEventLoopGroup();  try{  ServerBootstrap b = new ServerBootstrap();  b.group(bossGroup, workerGroup)  .channel(NioServerSocketChannel.class)  .option(ChannelOption.SO_BACKLOG, 1024)  .handler(new LoggingHandler(LogLevel.INFO))  .childHandler(new ChannelInitializer<SocketChannel>() {  @Override  protected void initChannel(SocketChannel ch) throws Exception {  ch.pipeline()  .addLast(new ProtobufVarint32FrameDecoder())                          .addLast(new ProtobufDecoder(  SubscribeReqProto.SubscribeReq.getDefaultInstance()))                         .addLast(new ProtobufVarint32LengthFieldPrepender())                          .addLast(new ProtobufEncoder())                       .addLast(new SubReqServerHandler());                          }  });  //绑定端口,同步等待成功  ChannelFuture f = b.bind(port).sync();  //等待服务端监听端口关闭
            f.channel().closeFuture().sync();  }finally{  //退出时释放资源
            bossGroup.shutdownGracefully();  workerGroup.shutdownGracefully();  }

以上是提供的默认实现。关键在于ProtobufVarint32FrameDecoder,ProtobufVarint32LengthFieldPrepender类。

二,ProtobufVarint32LengthFieldPrepender 编码类

An encoder that prepends the the Google Protocol Buffers 128 Varints integer length field.

* BEFORE DECODE (300 bytes)       AFTER DECODE (302 bytes)
* +---------------+               +--------+---------------+
* | Protobuf Data |-------------->| Length | Protobuf Data | * | (300 bytes) | | 0xAC02 | (300 bytes) | * +---------------+ +--------+---------------+

从类的说明来看, proto 消息格式如:Length + Protobuf Data (消息头+消息数据) 方式,这里特别需要注意的是头长使用的是varints方式不是int ,消息头描述消息数据体的长度。为了更减少传输量,消息头采用的是varint 格式。

什么是varint?

文章来源http://www.cnblogs.com/tankaixiongVarint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。 Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,会用两个字节。

更多可参见我上篇文章

最大的区别是消息头它不是固定长度(常见是的使用INT 4个字节固定长度),Varint它用一个或多个字节来表示一个数字决定它不是固定长度!

ProtobufVarint32LengthFieldPrepender 类的主要方法如下:

@Overrideprotected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {int bodyLen = msg.readableBytes();int headerLen = CodedOutputStream.computeRawVarint32Size(bodyLen);out.ensureWritable(headerLen + bodyLen);CodedOutputStream headerOut =CodedOutputStream.newInstance(new ByteBufOutputStream(out), headerLen);headerOut.writeRawVarint32(bodyLen);headerOut.flush();out.writeBytes(msg, msg.readerIndex(), bodyLen);}

CodedOutputStream 主要是针对与varints相关操作类。 先看是如何写消息头的,得到bodyLen 消息体长度然后调用computeRawVarint32Size()计算需要多少个字节,

public static int computeRawVarint32Size(final int value) {if ((value & (0xffffffff <<  7)) == 0) return 1;if ((value & (0xffffffff << 14)) == 0) return 2;if ((value & (0xffffffff << 21)) == 0) return 3;if ((value & (0xffffffff << 28)) == 0) return 4;return 5;}

0xffffffff << 7 二进制表示11111111111111111111111110000000 ,当与value &计算=0则表示value最大只会是000000000000000000000001111111,一个字节足以。

通过&运算得出使用多少个字节就可以表示当前数字。左移7位是与Varint定义相关,第一位需要保留给标识(1表示后续的 byte 也是该数字的一部分,0则结束)。要表示 int 32位 和多加的每个字节第一个标识位,多出了4位,所以就最大会有5个字节。

得到了varints值,然后如何写入out? 再看关键方法writeRawVarint32()。

public void writeRawVarint32(int value) throws IOException {while (true) {//0x7F为127if ((value & ~0x7F) == 0) {//是否小于127,小于则一个字节就可以表示了
        writeRawByte(value);return;} else {writeRawByte((value & 0x7F) | 0x80);//因不于小127,加一高位标识value >>>= 7;//右移7位,再递归
      }}}/** Write a single byte. */public void writeRawByte(final byte value) throws IOException {if (position == limit) {refreshBuffer();}buffer[position++] = value;}private void refreshBuffer() throws IOException {if (output == null) {// We're writing to a single buffer.throw new OutOfSpaceException();}// Since we have an output stream, this is our buffer// and buffer offset == 0output.write(buffer, 0, position);position = 0;}

byte 的取值(-128~127) , 0x7F为127 , 0x80为128

循环取后7位,如果小于127则结束,不小于第一位加标识位1。 因为循环右移所以,实际位置颠倒了,解码时需要倒过来再拼接。

消息头因为是varint32可变字节,所以比较复杂些,消息体简单直接writeBytes即可。

二,ProtobufVarint32FrameDecoder 解码类

同样对应CodedOutputStream有CodedInputStream类,操作解码时的varints。

@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {in.markReaderIndex();final byte[] buf = new byte[5];for (int i = 0; i < buf.length; i ++) {if (!in.isReadable()) {in.resetReaderIndex();return;}buf[i] = in.readByte();if (buf[i] >= 0) {int length = CodedInputStream.newInstance(buf, 0, i + 1).readRawVarint32();if (length < 0) {throw new CorruptedFrameException("negative length: " + length);}if (in.readableBytes() < length) {in.resetReaderIndex();return;} else {out.add(in.readBytes(length));return;}}}// Couldn't find the byte whose MSB is off.throw new CorruptedFrameException("length wider than 32-bit");}

前面说明了最大长度为5个字节所以这里声明了5个长度的字节来读取消息头。

buf[i] >= 0 这里为什么是>0然后就可以解码了呢?

还是这句话:varints第一位表示后续的byte是否是该数字的一部分!

如果字节第一位为1则表示后续还有字节是表示消息头,当这个字节的第一位为1则这个字节肯定是负数(字节最高位表示正负),大于等于0表示描述消息体长度的数字已经读完了。

然后调用readRawVarint32() 还原成int ,与之前 writeRawVarint32()反其道而行。

public int readRawVarint32() throws IOException {byte tmp = readRawByte();if (tmp >= 0) {return tmp;}int result = tmp & 0x7f;if ((tmp = readRawByte()) >= 0) {result |= tmp << 7;} else {result |= (tmp & 0x7f) << 7;if ((tmp = readRawByte()) >= 0) {result |= tmp << 14;} else {result |= (tmp & 0x7f) << 14;if ((tmp = readRawByte()) >= 0) {result |= tmp << 21;} else {result |= (tmp & 0x7f) << 21;result |= (tmp = readRawByte()) << 28;if (tmp < 0) {// Discard upper 32 bits.for (int i = 0; i < 5; i++) {if (readRawByte() >= 0) {return result;}}throw InvalidProtocolBufferException.malformedVarint();}}}}return result;}

取第N字节左移7*N位或|第一个字节拼接,实现了倒序拼接,最后得到了消息体长度。然后根据得到的消息体长度读取数据,如果消息体长度不够则回滚到markReaderIndex,等待数据。

四,总结

文章来源http://www.cnblogs.com/tankaixiong本文主要详细介绍了netty 对 protobuf 协议的解码与包装。重点在消息头 varint32的 算法表示上进行了说明。了解了varint32在协议中的实现,方便应用在其语言对接。

netty 对 protobuf 协议的解码与包装探究(2)相关推荐

  1. netty 基于 protobuf 协议 实现 websocket 版本的简易客服系统

    https://segmentfault.com/a/1190000017464313 netty 基于 protobuf 协议 实现 websocket 版本的简易客服系统 结构 netty 作为服 ...

  2. Dubbo篇:基于Netty实现Dubbo协议编解码源码分析

    Dubbo协议解析 Dubbo协议设计参考了TCP/IP协议,包括协议头和协议体两部分.16字节报文头主要携带了魔法数(0xdabb,用于分割两个不同请求),以及当前请求报文是否是Request.Re ...

  3. netty系列之:在netty中使用protobuf协议

    文章目录 简介 定义protobuf 定义handler 设置ChannelPipeline 构建client和server端并运行 总结 简介 netty中有很多适配不同协议的编码工具,对于流行的g ...

  4. Netty使用篇:Http协议编解码

    第一章:概述 Http协议是一个应用层协议.在Http协议之上又构建出来了WebSocket这种双向通信的协议.可以主动在服务端帮我们去推数据,实际上我们现在做一些双向通信的比较很重要的东西,比如:推 ...

  5. SuperSocket与Netty之实现protobuf协议,包括服务端和客户端

    今天准备给大家介绍一个c#服务器框架(SuperSocket)和一个c#客户端框架(SuperSocket.ClientEngine).这两个框架的作者是园区里面的江大渔. 首先感谢他的无私开源贡献. ...

  6. http协议解决粘包拆包半包 的编码解码过程、 以及netty 使用http协议的原理

    本文主要介绍netty对http协议解析原理,着重讲解keep-alive,gzip,truncked等机制,详细描述了netty如何实现对http解析的高性能. 1 http协议 1.1 描述 标示 ...

  7. Spring Boot 整合 Netty和Protobuf

    前言 本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容.Protobuf会简单的介绍下用法. 介绍 protocol buffer(以下简称PB)是 ...

  8. 07 接头暗语:如何利用 Netty 实现自定义协议通信?

    文章目录 07 接头暗语:如何利用 Netty 实现自定义协议通信? 通信协议设计 1. 魔数 2. 协议版本号 3. 序列化算法 4. 报文类型 5. 长度域字段 6. 请求数据 7. 状态 8. ...

  9. 07 | 接头暗语:如何利用 Netty 实现自定义协议通信?

    既然是网络编程,自然离不开通信协议,应用层之间通信需要实现各种各样的网络协议.在项目开发的过程中,我们就需要去构建满足自己业务场景的应用层协议.在上节课中我们介绍了如何使用网络协议解决 TCP 拆包/ ...

最新文章

  1. python max()_Python Decimal max()用法及代码示例
  2. java Bean 不需要自动生成get和set
  3. java.lang.RuntimeException: Handler (com.***.behavior.BEvent$1) {421bca80} sending message to a Hand
  4. oracle+11g+rda,Oracle RDA 4.20 初体验
  5. LCD 设备驱动框架分析及核心结构
  6. php中括号的优先级是不是最高的,理解php中操作符的优先级和结合性
  7. css隐藏输入框的光标
  8. CCF 201512-2 消除类游戏
  9. mongodb备份每一天的数据
  10. java中关于时间的格式化
  11. Web.xml配置详解之context-param (加载spring的xml,然后初始化bean看的)
  12. 《逻辑说服力》— 综合素质提升书籍
  13. SPPNet算法解析
  14. 【CSDN软件工程师能力认证学习精选】Python网络编程之初识
  15. Java中数据库模糊查询写法
  16. 视频批量添加水印的方法
  17. python前面三个大于号是啥_JavaScript 无符号位移运算符 三个大于号 的使用方法...
  18. 口袋电子秤方案芯片CSU18P88
  19. vue3监听网页窗口关闭
  20. Java 计算时间差

热门文章

  1. 电信运营商计费模型_商客通:电信400电话怎么办理
  2. java门户网站项目代码_基于jsp的企业门户网站-JavaEE实现企业门户网站 - java项目源码...
  3. matlab 向量模量,有限元分析简单实例之平面矩形薄板(matlab)
  4. python爬虫代理池_python爬虫之ProxyPool(代理ip地址池的构建)
  5. java十进制小数转化为二进制小数代码 乘二取整法_(四)改掉这些坏习惯,还怕写不出健壮的代码?...
  6. 静电对于机电设备的影响
  7. 关于智能车竞赛程序公正问题的讨论
  8. RT-Thread逐飞-智能车培训之RT-Thread在全向行进组中的应用
  9. 2021年春季学期-信号与系统-第二次作业参考答案-第五小题
  10. 高频小功率三极管-S9018