netty系列之:自定义编码和解码器要注意的问题
简介
在之前的系列文章中,我们提到了netty中的channel只接受ByteBuf类型的对象,如果不是ByteBuf对象的话,需要用编码和解码器对其进行转换,今天来聊一下netty自定义的编码和解码器实现中需要注意的问题。
自定义编码器和解码器的实现
在介绍netty自带的编码器和解码器之前,告诉大家怎么实现自定义的编码器和解码器。
netty中所有的编码器和解码器都是从ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter衍生而来的。
对于ChannelOutboundHandlerAdapter来说,最重要的两个类是MessageToByteEncoder 和 MessageToMessageEncoder 。
MessageToByteEncoder是将消息编码成为ByteBuf,这个类也是我们自定义编码最常用的类,直接继承这个类并实现encode方法即可。注意到这个类有一个泛型,这个泛型指定的就是消息的对象类型。
例如我们想将Integer转换成为ByteBuf,可以这样写:
public class IntegerEncoder extends MessageToByteEncoder<Integer> {
@Override
public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out)
throws Exception {
out.writeInt(msg);
}
}
MessageToMessageEncoder是在消息和消息之间进行转换,因为消息并不能直接写入到channel中,所以需要和MessageToByteEncoder配合使用。
下面是一个Integer到String的例子:
public class IntegerToStringEncoder extends
MessageToMessageEncoder<Integer> {
@Override
public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out)
throws Exception {
out.add(message.toString());
}
}
对于ChannelInboundHandlerAdapter来说,最重要的两个类是ByteToMessageDecoder和MessageToMessageDecoder 。
ByteToMessageDecoder是将ByteBuf转换成对应的消息类型,我们需要继承这个类,并实现decode方法,下面是一个从ByteBuf中读取所有可读的字节,并将结果放到一个新的ByteBuf中,
public class SquareDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
out.add(in.readBytes(in.readableBytes()));
}
}
MessageToMessageDecoder是消息和消息之间的转换,同样的只需要实现decode方法即可,如下从String转换到Integer:
public class StringToIntegerDecoder extends
MessageToMessageDecoder<String> {
@Override
public void decode(ChannelHandlerContext ctx, String message,
List<Object> out) throws Exception {
out.add(message.length());
}
}
ReplayingDecoder
上面的代码看起来很简单,但是在实现的过程中还有一些问题要注意。
对于Decoder来说,我们从ByteBuf中读取数据,然后进行转换。但是在读取的过程中,并不知道ByteBuf中数据的变动情况,有可能在读取的过程中ByteBuf还没有准备好,那么就需要在读取的时候对ByteBuf中可读字节的大小进行判断。
比如我们需要解析一个数据结构,这个数据结构的前4个字节是一个int,表示后面byte数组的长度,我们需要先判断ByteBuf中是否有4个字节,然后读取这4个字节作为Byte数组的长度,然后再读取这个长度的Byte数组,最终得到要读取的结果,如果其中的某一步出现问题,或者说可读的字节长度不够,那么就需要直接返回,等待下一次的读取。如下所示:
public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception {
if (buf.readableBytes() < 4) {
return;
}
buf.markReaderIndex();
int length = buf.readInt();
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return;
}
out.add(buf.readBytes(length));
}
}
这种判断是比较复杂同时也是可能出错的,为了解决这个问题,netty提供了 ReplayingDecoder用来简化上面的操作,在ReplayingDecoder中,假设所有的ByteBuf已经处于准备好的状态,直接从中间读取即可。
上面的例子用ReplayingDecoder重写如下:
public class IntegerHeaderFrameDecoder
extends ReplayingDecoder<Void> {
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception {
out.add(buf.readBytes(buf.readInt()));
}
}
它的实现原理是去尝试读取对应的字节信息,如果没有读到,则抛出异常,ReplayingDecoder接收到异常之后,会重新调用decode方法。
虽然ReplayingDecoder使用起来非常简单,但是它有两个问题。
第一个问题是性能问题,因为会去重复调用decode方法,如果ByteBuf本身并没有变化,就会导致重复decode同一个ByteBuf,照成性能的浪费。解决这个问题就是在在decode的过程中分阶段进行,比如上面的例子中,我们需要先读取Byte数组的长度,然后再读取真正的byte数组。所以在读完byte数组长度之和,可以调用checkpoint()方法做一个保存点,下次再执行decode方法的时候就可以跳过这个保存点,继续后续的执行过程,如下所示:
public enum MyDecoderState {
READ_LENGTH,
READ_CONTENT;
}
public class IntegerHeaderFrameDecoder
extends ReplayingDecoder<MyDecoderState> {
private int length;
public IntegerHeaderFrameDecoder() {
// Set the initial state.
super(MyDecoderState.READ_LENGTH);
}
@Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception {
switch (state()) {
case READ_LENGTH:
length = buf.readInt();
checkpoint(MyDecoderState.READ_CONTENT);
case READ_CONTENT:
ByteBuf frame = buf.readBytes(length);
checkpoint(MyDecoderState.READ_LENGTH);
out.add(frame);
break;
default:
throw new Error("Shouldn't reach here.");
}
}
}
第二个问题是同一个实例的decode方法可能会被调用多次,如果我们在ReplayingDecoder中有私有变量的话,则需要考虑对这个私有变量的清洗工作,避免多次调用造成的数据污染。
总结
通过继承上面的几个类,我们就可以自己实现编码和解码的逻辑了。但是好像还有点问题,自定义编码和解码器是不是太复杂了?还需要判断要读取的byte数组的大小。有没有更加简单的方法呢?
有的,敬请期待netty系列的下一篇文章:netty自带的编码器和解码器.
本文的例子可以参考:learn-netty4
本文已收录于 http://www.flydean.com/14-netty-cust-codec/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
netty系列之:自定义编码和解码器要注意的问题的更多相关文章
- netty系列之:自定义编码解码器
目录 简介 自定义编码器 自定义解码器 添加编码解码器到pipeline 计算2的N次方 总结 简介 在之前的netty系列文章中,我们讲到了如何将对象或者String转换成为ByteBuf,通过使用 ...
- netty系列之:内置的Frame detection
目录 简介 Frame detection DelimiterBasedFrameDecoder FixedLengthFrameDecoder LengthFieldBasedFrameDecode ...
- netty系列之:netty中的frame解码器
目录 简介 LineBasedFrameDecoder DelimiterBasedFrameDecoder FixedLengthFrameDecoder LengthFieldBasedFrame ...
- netty系列之:netty中的核心编码器base64
目录 简介 netty codec的实现逻辑 netty中Base64的实现 netty中的base64编码和解码器 Base64Encoder Base64Decoder 总结 简介 我们知道数据在 ...
- netty系列之:netty中的核心编码器bytes数组
目录 简介 byte是什么 netty中的byte数组的工具类 netty中byte的编码器 总结 简介 我们知道netty中数据传输的核心是ByteBuf,ByteBuf提供了多种数据读写的方法,包 ...
- netty系列之:netty中的核心解码器json
目录 简介 java中对json的支持 netty对json的解码 总结 简介 程序和程序之间的数据传输方式有很多,可以通过二进制协议来传输,比较流行的像是thrift协议或者google的proto ...
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
- netty系列之:使用netty搭建websocket服务器
目录 简介 netty中的websocket websocket的版本 FrameDecoder和FrameEncoder WebSocketServerHandshaker WebSocketFra ...
- netty系列之:分离websocket处理器
目录 简介 netty的消息处理 处理WebSocketFrame 处理HTTP 编码和解码器 总结 简介 在上一篇文章中,我们使用了netty构建了可以处理websocket协议的服务器,在这个服务 ...
随机推荐
- Java:Java的<<<移位运算符详解
1) 左移运算(<<) 左移就是把所有位向左移动几位 如: 12 << 2 意思就是12向左移动两位 12的二进制是: 0000 1100 通过这个图我们 ...
- CG-CTF Our 16bit Games
一.放到xp上面跑,发现是一个图形界面的飞机游戏...估计是分数到达多少,然后就可以输出flag. 打开ida,一脸懵逼,主要这玩意16位,我直接静态调试了 发现很多汇编代码,有点懵逼,在最下方的地方 ...
- Spring MVC中的M V C
M→Model 模型 V→View 视图 C→Controller 控制器 也就是说一次交互由生到死(请求到相应) 需要经过 这三个层级 来完成 那么为什么这么设计 这么设计又有什么好处 我是这么认为 ...
- [刘阳Java]_Web前端入门级练习_迅雷首页第一屏设计
今天接着上一篇文章<Web前端入门级练习_迅雷官宣网设计>正式开始迅雷首页第一版的设计.如果完成,则最终的效果图如下 第一步:先完成logo部分的设计 logo设计,我们会使用CSS的定位 ...
- 【有奖互动】HMS Core. Sparkle游戏应用创新沙龙,诚邀您参与
活动简介 随着互联网基础设施的完善和"宅经济"效应凸显,游戏行业逆势上扬,迎来巨大消费市场.同时,用户需求愈加多样化,如何进一步创新和技术升级.提升核心竞争力已成为游戏开发与运营的 ...
- 【剑指offer】50.数组中重复出现的数字
50.数组中重复出现的数字 知识点:数组:Set的不可重复性 题目描述 在一个长度为n的数组里的所有数字都在0到n-1的范围内. 数组中某些数字是重复的,但不知道有几个数字是重复的.也不知道每个数字重 ...
- selenium3 + python - js&jquery操作处理
# 推荐学习:https://www.w3school.com.cn/js/index.asp## 下面以简书登录&注册定位元素为例"""js定位 id name ...
- Leetcode2.两数相加——简洁易懂
> 简洁易懂讲清原理,讲不清你来打我~ 输入两个链表,相同位置相加,进位给下一个位置,输出相加后的链表![在这里插入图片描述](https://img-blog.csdnimg.cn/f43b7 ...
- Docker编排利器DockerCompose
Docker 编排利器 DockerCompose,编排之后可以一次性通过一条命令启动一组服务 例如一条命令启动 SpringBoot 项目,同时启动 SpringBoot 项目依赖的其他中间件(My ...
- 【Lucas组合数定理+中国剩余定理】Mysterious For-HDU 4373
Mysterious For-HDU 4373 题目描述 MatRush is an ACMer from ZJUT, and he always love to create some specia ...