Netty-ProtobufVarint32
效果
ProtobufVarint32LengthFieldPrepender编码器用于在数据最前面添加
Varint32,表示数据长度
ProtobufVarint32FrameDecoder是相对应的解码器

Varint32
讲编码器之前,先来讲讲什么是VarInt32(vary int 32),即:可变长的int
在java里,int的长度固定为 4 byte,即 32 bits,最高位为符号位。
而Varint32则不固定长度,最小 1 byte,最大 5 byte,每个byte的最高位如果为1表示下一个byte依然属于Varint32的,为0表示Varint32到当前byte结束。
所以在Varint32中,每个byte只有7bit存储数据。
下面以 398 举例:
// 数字398的二进制表示
110001110
// 在java中int的二进制表示
00000000 00000000 00000001 10001110
// Varint32的二进制表示
10001110 00000011
转换步骤如下:
- 398的二进制表示为110001110,总共有9位
- 因为398长度为9bit,大于7,所以在Varint32中需要2个byte来存储
- Varint32第一个byte存398的低7位0001110,最高位置1表示还未存储完成,即:10001110
- Varint32第二个byte存398的后面两位11,最高位置0表示已存储完成,00000011
最后,别问我负数怎么表示,问神奇的海螺。
ProtobufVarint32LengthFieldPrepender
此编码器的作用,就是将数据长度从int转成Varint32,并添加在数据流的最前面。
查看源码,其入口为decode方法:
@Override
protected void encode(
ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
// 获取数据长度
int bodyLen = msg.readableBytes();
// 计算int转成Varint32所需字节
int headerLen = computeRawVarint32Size(bodyLen);
// 安全扩充缓冲区
out.ensureWritable(headerLen + bodyLen);
// 将bodyLen转成Varint32并写入
writeRawVarint32(out, bodyLen);
// 写入原有的数据msg
out.writeBytes(msg, msg.readerIndex(), bodyLen);
}
先来看看如何计算int转成Varint32所需字节
static int computeRawVarint32Size(final int value) {
// value的低7位有数据,其余位为0
if ((value & (0xffffffff << 7)) == 0) {
return 1;
}
// value的低14位有数据,其余位为0
if ((value & (0xffffffff << 14)) == 0) {
return 2;
}
if ((value & (0xffffffff << 21)) == 0) {
return 3;
}
if ((value & (0xffffffff << 28)) == 0) {
return 4;
}
return 5;
}
// 0xffffffff 的二进制表示
11111111 11111111 11111111 11111111
// 0xffffffff << 7 的二进制表示
11111111 11111111 11111111 10000000
// 假设value为100,二进制表示
00000000 00000000 00000000 01100100
// value与0xffffffff << 7按位与,即value & (0xffffffff << 7)
11111111 11111111 11111111 10000000
&
00000000 00000000 00000000 01100100
=
00000000 00000000 00000000 00000000 // 结果等于0
// 假设value为398,value与0xffffffff << 7按位与
11111111 11111111 11111111 10000000
&
00000000 00000000 00000001 10001110
=
00000000 00000000 00000001 10000000 // 结果等于384,大于0
再来看看将bodyLen转成Varint32并写入
static void writeRawVarint32(ByteBuf out, int value) {
while (true) {
// value的低7位有数据,其余位为0
if ((value & ~0x7F) == 0) {
out.writeByte(value);
return;
} else {
// 取value的低7位,最高位置1
out.writeByte((value & 0x7F) | 0x80);
// 右移7位
value >>>= 7;
}
}
}
ProtobufVarint32FrameDecoder
此解码器的作用,是将Varint32 + data,转换成 data
源码入口
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
// 标记读取索引,用户后续的恢复
in.markReaderIndex();
// 读取前的索引位置
int preIndex = in.readerIndex();
// 读取字节,将Varint32转成int
int length = readRawVarint32(in);
// 读取后索引位置等于读取前,表示读取不成功
if (preIndex == in.readerIndex()) {
return;
}
if (length < 0) {
throw new CorruptedFrameException("negative length: " + length);
}
// 如果netty读取到的字节长度不满足数据长度,则重置读取索引
if (in.readableBytes() < length) {
in.resetReaderIndex();
} else {
out.add(in.readRetainedSlice(length));
}
}
读取字节,将Varint32转成int
private static int readRawVarint32(ByteBuf buffer) {
if (!buffer.isReadable()) {
return 0;
}
buffer.markReaderIndex();
byte tmp = buffer.readByte();
// tmp >= 0,则byte最高位为0,证明Varint32长度为1byte
if (tmp >= 0) {
return tmp;
} else {
// result取temp的低7位
int result = tmp & 127;
// Varint还没结束,但netty读不到更多字节了,则返回
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
// 读取下一个字节,并判断Varint是否在该字节结束
if ((tmp = buffer.readByte()) >= 0) {
result |= tmp << 7;
} else {
// 如果Varint在第二个字节还没结束,则取第二个字节的低7位
result |= (tmp & 127) << 7;
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
if ((tmp = buffer.readByte()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 127) << 14;
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
if ((tmp = buffer.readByte()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 127) << 21;
if (!buffer.isReadable()) {
buffer.resetReaderIndex();
return 0;
}
result |= (tmp = buffer.readByte()) << 28;
if (tmp < 0) {
throw new CorruptedFrameException("malformed varint.");
}
}
}
}
return result;
}
}
Netty-ProtobufVarint32的更多相关文章
- 谈谈如何使用Netty开发实现高性能的RPC服务器
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...
- 基于netty http协议栈的轻量级流程控制组件的实现
今儿个是冬至,所谓“冬大过年”,公司也应景五点钟就放大伙儿回家吃饺子喝羊肉汤了,而我本着极高的职业素养依然坚持留在公司(实则因为没饺子吃没羊肉汤喝,只能呆公司吃食堂……).趁着这一个多小时的时间,想跟 ...
- 从netty-example分析Netty组件续
上文我们从netty-example的Discard服务器端示例分析了netty的组件,今天我们从另一个简单的示例Echo客户端分析一下上个示例中没有出现的netty组件. 1. 服务端的连接处理,读 ...
- 源码分析netty服务器创建过程vs java nio服务器创建
1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...
- 从netty-example分析Netty组件
分析netty从源码开始 准备工作: 1.下载源代码:https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程. netty提 ...
- Netty实现高性能RPC服务器优化篇之消息序列化
在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...
- Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇
目前业界流行的分布式消息队列系统(或者可以叫做消息中间件)种类繁多,比如,基于Erlang的RabbitMQ.基于Java的ActiveMQ/Apache Kafka.基于C/C++的ZeroMQ等等 ...
- 基于Netty打造RPC服务器设计经验谈
自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...
- Netty构建分布式消息队列实现原理浅析
在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
- JAVA通信系列三:Netty入门总结
一.Netty学习资料 书籍<Netty In Action中文版> 对于Netty的十一个疑问http://news.cnblogs.com/n/205413/ 深入浅出Nettyhtt ...
随机推荐
- APP应用前端开发
1.开发手机APP前端要重视meta标签的编写: 2.注意HTML5标签在前端开发中的使用: 3.前端制作要舍弃CSS float属性(可flex布局),用绝对定位不利于页面布局的扩展: 4.APP前 ...
- Java学习笔记-基础语法Ⅹ-进程线程
学习快一个月了,现在学到了黑马Java教程的300集 打印流的特点: 只负责输出数据,不负责读取数据 有自己的特有方法 字节打印流:PrintStream,使用指定的文件名创建新的打印流 import ...
- 海量数据存储ClickHouse
ClickHouse介绍 ClickHouse的由来和应用场景 俄罗斯Yandex在2016年开源,使用C++编写的列式存储数据库,近几年在OLAP领域大范围应用 官网:https://clickho ...
- 思科,华为l2tp组网,家庭宽带,公司内网数据分离
拓扑 简介 办公网使用专线接入,拥有固定IP地址,网络出口使用防火墙做NAT,下联交换机接入服务器,办公电脑等,旁挂思科路由器做L2TP LNS 家庭宽带使用ADSL线路,华为路由器做拨号设备与DHC ...
- 用c语言调用Easy X实现图像的输出,附带音乐的读取
要实现此功能需要用EasyX 一.下载VS编译环境和EasyX. ①Vs2019https://iwx.mail.qq.com/ftn/download?func=3&key=9e9b6c33 ...
- 搞定了!OAuth2使用验证码进行授权
现在验证码登录已经成为很多应用的主流登录方式,但是对于OAuth2授权来说,手机号验证码处理用户认证就非常繁琐,很多同学却不知道怎么接入. 认真研究胖哥Spring Security OAuth2专栏 ...
- 110_Power Pivot特殊结算日期及财年日期
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 1.背景 前几天看到群里有朋友在搞特殊结算日期,主要不是按照正常日期里的整月,按照比如:上月21号至本月20号作为结算周期 ...
- shellcode编写
shellcode编写 shellcode是一段用于利用软件漏洞而执行的代码,通常使用机器语言编写,其目的往往是让攻击者获得目标机器的命令行shell而得名,其他有类似功能的代码也可以称为shellc ...
- 【NFLSPC#4】嘉然今天吃什么(踩标做法)
[NFLSPC#4]嘉然今天吃什么 感谢 @zhoukangyang 神仙的帮助. Solution 令 \(s_i\) 表示选了 \(i\) 个灯后仍然不合法的概率,那么 \(E(x)=\sum_{ ...
- Docker运行资源控制
概述 一个 docker host 上会运行若干容器,每个容器都需要 CPU.内存和 IO 资源.对于 KVM,VMware 等虚拟化技术,用户可以控制分配多少 CPU.内存资源给每个虚拟机.对于 ...