netty系列之:自定义编码解码器
简介
在之前的netty系列文章中,我们讲到了如何将对象或者String转换成为ByteBuf,通过使用netty自带的encoder和decoder可以实现非常方便的对象和ByteBuf之间的转换,然后就可以向channel中随意写入对象和字符串了。
使用netty自带的编码器当然很好,但是如果你有些特殊的需求,比如希望在编码的过程中对数据进行变换,或者对对象的字段进行选择,那么可能就需要自定义编码解码器了。
自定义编码器
自定义编码器需要继承MessageToByteEncoder 类,并实现encode方法,在该方法中写入具体的编码逻辑。
本例我们希望计算2的N次方,据说将一张纸折叠100次可以达到地球到月亮的高度,这么大的数据普通的number肯定是装不下的,我们将会使用BigInteger来对这个巨大的数字进行保存。
那么对于被编码器来说,则需要将这个BigInteger转换成为byte数组。同时在byte数组读取的过程中,我们需要界定到底哪些byte数据是属于同一个BigInteger的,这就需要对写入的数据格式做一个约定。
这里我们使用三部分的数据结构来表示一个BigInteger。第一部分是一个magic word也就是魔法词,这里我们使用魔法词“N”,当读取到这个魔法词就表示接下来的数字是BigInteger。第二部分是表示bigInteger数字的byte数组的长度,获取到这个长度值,就可以读取到所有的byte数组值,最后将其转换成为BigInteger。
因为BigInteger是Number的子类,为了更加泛化编码器,我们使用Number作为MessageToByteEncoder的泛型,核心编码代码如下:
protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) {
// 将number编码成为ByteBuf
BigInteger v;
if (msg instanceof BigInteger) {
v = (BigInteger) msg;
} else {
v = new BigInteger(String.valueOf(msg));
}
// 将BigInteger转换成为byte[]数组
byte[] data = v.toByteArray();
int dataLength = data.length;
// 将Number进行编码
out.writeByte((byte) 'N'); // 魔法词
out.writeInt(dataLength); // 数组长度
out.writeBytes(data); // 最终的数据
}
自定义解码器
有了编码之后的byte数组,就可以在解码器中对其解码了。
上一节介绍了,编码过后的数据格式是魔法词N+数组长度+真正的数据。
其中魔法词长度是一个字节,数组长度是四个字节,前面部分总共是5个字节。所以在解码的时候,首先判断ByteBuf中可读字节的长度是否小于5,如果小于5说明数据是无效的,可以直接return。
如果可读字节的长度大于5,则表示数据是有效的,可以进行数据的解码了。
解码过程中需要注意的是,并不是所有的数据都是我们所希望的格式,如果在读取的过程中读到了我们不认识的格式,那么说明这个数据并不是我们想要的,则可以交由其他的handler进行处理。
但是对于ByteBuf来说,一旦调用read方法,就会导致reader index移动位置,所以在真正的读取数据之前需要调用ByteBuf的markReaderIndex方法,对readerIndex进行记录。然后分别读取魔法词、数组长度和剩余的数据,最后将数据转换成为BigInteger,如下所示:
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 保证魔法词和数组长度有效
if (in.readableBytes() < 5) {
return;
}
in.markReaderIndex();
// 检查魔法词
int magicNumber = in.readUnsignedByte();
if (magicNumber != 'N') {
in.resetReaderIndex();
throw new CorruptedFrameException("无效的魔法词: " + magicNumber);
}
// 读取所有的数据
int dataLength = in.readInt();
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
// 将剩下的数据转换成为BigInteger
byte[] decoded = new byte[dataLength];
in.readBytes(decoded);
out.add(new BigInteger(decoded));
}
添加编码解码器到pipeline
有了两个编码解码器,还需要将其添加到pipeline中进行调用。
在实现ChannelInitializer中的initChannel中,可以对ChannelPipeline进行初始化,本例中的初始化代码如下:
// 对流进行压缩
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// 添加number编码解码器
pipeline.addLast(new NumberDecoder());
pipeline.addLast(new NumberEncoder());
// 添加业务处理逻辑
pipeline.addLast(new CustomProtocolServerHandler());
其中最后一行是真正的业务处理逻辑,NumberDecoder和NumberEncoder是编码和解码器。这里我们还使用了一个ZlibEncoder用于对流数据进行压缩,这里使用的压缩方式是GZIP。
压缩的好处就是可以减少数据传输的数量,提升传输效率。其本质也是一个编码解码器。
计算2的N次方
计算2的N次方的逻辑是这样的,首先客户端发送2给服务器端,服务器端接收到该消息和结果1相乘,并将结果写回给客户端,客户端收到消息之后再发送2给服务器端,服务器端将上次的计算结果乘以2,再发送给客户端,以此类推直到执行N次。
首先看下客户端的发送逻辑:
// 最大计算2的1000次方
ChannelFuture future = null;
for (int i = 0; i < 1000 && next <= CustomProtocolClient.COUNT; i++) {
future = ctx.write(2);
next++;
}
当next小于等于要计算的COUNT时,就将2写入到channel中。
对于服务器来说,在channelRead0方法中,读取消息,并将其和结果相乘,再把结果写回给客户端。
public void channelRead0(ChannelHandlerContext ctx, BigInteger msg) throws Exception {
// 将接收到的msg乘以2,然后返回给客户端
count++;
result = result.multiply(msg);
ctx.writeAndFlush(result);
}
客户端统计读取到的消息个数,如果消息个数=COUNT,说明计算完毕,就可以将结果保存起来供后续使用,其核心代码如下:
public void channelRead0(ChannelHandlerContext ctx, final BigInteger msg) {
receivedMessages ++;
if (receivedMessages == CustomProtocolClient.COUNT) {
// 计算完毕,将结果放入answer中
ctx.channel().close().addListener(future -> {
boolean offered = answer.offer(msg);
assert offered;
});
}
}
总结
本文实现了一个Number的编码解码器,事实上你可以自定义实现任何对象的编码解码器。
本文的例子可以参考:learn-netty4
本文已收录于 http://www.flydean.com/13-netty-customprotocol/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:自定义编码解码器的更多相关文章
- netty系列之:自定义编码和解码器要注意的问题
目录 简介 自定义编码器和解码器的实现 ReplayingDecoder 总结 简介 在之前的系列文章中,我们提到了netty中的channel只接受ByteBuf类型的对象,如果不是ByteBuf对 ...
- netty系列之:netty中常用的对象编码解码器
目录 简介 什么是序列化 重构序列化对象 序列化不是加密 使用真正的加密 使用代理 Serializable和Externalizable的区别 netty中对象的传输 ObjectEncoder O ...
- netty系列之:netty中常用的字符串编码解码器
目录 简介 netty中的字符串编码解码器 不同平台的换行符 字符串编码的实现 总结 简介 字符串是我们程序中最常用到的消息格式,也是最简单的消息格式,但是正因为字符串string太过简单,不能附加更 ...
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
- Netty 系列(三)Netty 入门
Netty 系列(三)Netty 入门 Netty 是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络服务器和客户端程序.更多请参考:Netty Github 和 Netty中文 ...
- 7. 彤哥说netty系列之Java NIO核心组件之Selector
--日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第七篇. 简介 上一章我们一起学习了Java NIO的核心组件Buffer,它通常跟Channel一起使用,但是它们在网络IO中又该如何 ...
- Netty系列之源码解析(一)
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel ...
- netty系列之:netty架构概述
目录 简介 netty架构图 丰富的Buffer数据机构 零拷贝 统一的API 事件驱动 其他优秀的特性 总结 简介 Netty为什么这么优秀,它在JDK本身的NIO基础上又做了什么改进呢?它的架构和 ...
- netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器
目录 简介 搭建netty服务器 DNS服务器的消息处理 DNS客户端消息请求 总结 简介 在前面的文章中,我们提到了使用netty构建tcp和udp的客户端向已经公布的DNS服务器进行域名请求服务. ...
随机推荐
- CRM系统选型时的参考哪些方面
企业不论在制定营销策略或是在进行CRM系统选型时,首先都是要了解自身的需求.每一家企业的情况和需求都有很大差异,CRM系统的功能也都各有偏重.有些CRM偏重销售管理.有些注重于营销自动化.有些则侧重于 ...
- 超详细的原生JavaScript轮播图(幻灯片)的制作
本次轮播图的制作主要分为3个部分,分别是:设置定时器自动轮播:点击左右切换按钮轮播:下方点击按钮轮播.具体实现步骤如下: (效果图) html部分代码如下: <div class="s ...
- 免费版:Xshell和Xftp下载路径
家庭版Xshell和Xftp下载地址: 下载地址:https://www.netsarang.com/zh/free-for-home-school/
- 数据连接池JNDI
数据库连接有很多中方式,JDBC数据库的连接方式,前边我们已经介绍过了,而开发中我们经常使用的是DataBaseConnectionPool(数据库连接池,DBCP).数据库连接池到底是什么?它比jd ...
- 安卓开发--探究碎片Fragment
简述: 最近做开发的时候又遇到了Fragment,发现太久没写,都快忘了,就抓紧写个笔记(我太懒的了233) Fragment可以简单的看成迷你的活动,它和活动一样都有布局和生命周期,它可以嵌入活动之 ...
- Python网页正文转换语音文件的操作方法
天气真的是越来越冷啦,有时候我们想翻看网页新闻,但是又冷的不想把手拿出来,移动鼠标翻看.这时候,是不是特别想电脑像讲故事一样,给我们念出来呢?人生苦短,我有python啊,试试用 Python 来朗读 ...
- 南京大学计算机基础 ELF和可执行文件格式
1.可重定位目标文件格式 主要是由ELF头,一些节比如.text节,.rodata节,.data节,.bss节等,前面是只读的,后面是可读可写的,加上一个节头表 1.1 ELF头里面主要包含了16字节 ...
- python 字符串 增、删、改、查基本操作
private static String TAG = "MainActivity"; private String str = " a,bB,1cCcc,2dDd d2 ...
- ctf之SusCTF2017-Caesar cipher
由题目名字SusCTF2017-Caesar cipher可知,该题目考察凯撒密码. 直接下载附件打开如图 由题目描述可知,提交的flag格式为Susctf{}.在网上搜索在凯撒密码解密. 偏移量为3 ...
- 远程连接Linux上的MongoDB服务
1.Linux环境上安装好MongoDB,并配置好环境变量 2.启动MongoDB 注:mongod /opt/michael/mongodb/mongodb-linux-x86_64-4.0.5/d ...