自定义Decoder继承ByteToMessageDecoder实现解码的小案例
ByteToMessageDecoder是一种ChannelInboundHandler,可以称为解码器,负责将byte字节流(ByteBuf)转换成一种Message,Message是应用可以自己定义的一种Java对象。
例如应用中使用protobuf协议,则可以将byte转换为Protobuf对象。然后交给后面的Handler来处理。
使用示例, 下面这段代码先将收到的数据按照换行符分割成一段一段的,然后将byte转换成String, 再将String转换成int, 然后把int加一后写回。

代码:
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
bootstrap.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG))
.group(bossGroup, workerGroup)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024))
.addLast(new ByteToStringDecoder())
.addLast(new StringToIntegerDecoder())
.addLast(new IntegerToByteEncoder())
.addLast(new IntegerIncHandler());
}
});
ChannelFuture bind = bootstrap.bind(8092);
bind.sync();
bind.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
}
这里的ChannelPipeline的组织结构是
- ByteToStringDecoder ==> 将byte转换成String的Decoder
- StringToIntegerDecoder ==>String转换成Integer对象的Decoder
- IntegerToByteEncoder ==>Integer转换成byte的Encoder
- IntegerIncHandler ==> 将接受到的int加一后返回
下面来逐一分析
ByteToStringMessageDecoder继承于ByteToMessageDecoder,并实现了ByteToMessageDecoder的
decode(ChannelHandlerContext ctx, ByteBuf in, java.util.List out)方法。</java.lang.object>decode方法实现中要求将ByteBuf中的数据进行解码然后将解码后的对象增加到list中:
public class ByteToStringDecoder extends ByteToMessageDecoder {
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
byte[] data = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(data);
list.add(new String(data, StandardCharsets.UTF_8));
}
}
ByteToStringMessageDecoder继承于ByteToMessageDecoder,并实现了ByteToMessageDecoder的
decode(ChannelHandlerContext ctx, ByteBuf in, java.util.List out)方法。
decode方法实现中要求将ByteBuf中的数据进行解码然后将解码后的对象增加到list中
ByteToMessageDecoder
ByteToMessageDecoder继承了ChannelInboundHandlerAdapter所以是一个处理Inbound事件的Handler。
其内部保存一个Cumulator用于保存待解码的ByteBuf,然后不断调用子类需要实现的抽象方法decode去取出byte数据转换处理。
/**
* Cumulate {@link ByteBuf}s.
*/
public interface Cumulator {
/**
* Cumulate the given {@link ByteBuf}s and return the {@link ByteBuf} that holds the cumulated bytes.
* The implementation is responsible to correctly handle the life-cycle of the given {@link ByteBuf}s and so
* call {@link ByteBuf#release()} if a {@link ByteBuf} is fully consumed.
*/
ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}
Cumulator有两种实现,MERGE_CUMULATOR和COMPOSITE_CMUMULATOR。MERGE_CUMULATOR通过memory copy的方法将in中的数据复制写入到cumulation中。COMPOSITE_CUMULATOR采取的是类似链表的方式,没有进行memory copy, 通过一种CompositeByteBuf来实现,在某些场景下会更适合。默认采用的是MERGE_CUMULATOR。
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
final ByteBuf buffer;
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
|| cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain() or if its read-only.
// 如果cumulation是只读的、或者要超过capacity了,或者还有其他地方在引用, 则都通过创建一个新的byteBuf的方式来扩容ByteBuf
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
buffer = cumulation;
}
buffer.writeBytes(in);
in.release();
return buffer;
}
};
ByteToMessageDecoder中最主要的部分在channelRead处理上
- 收到一个msg后先判断是否是ByteBuf类型,是的情况创建一个CodecOutputList(也是一种list)保存转码后的对象列表
- 如果cumulation为null则把msg设置为cumulation,否则合并到cumulation里
- 调用callDecode方法,尝试解码
- finally中如果cumulation已经读完了,就release并置为null等待gc
- 调用fireChannelRead将解码后的out传递给后面的Handler
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
cumulation = data;
} else {
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++ numReads >= discardAfterReads) {
// We did enough reads already try to discard some bytes so we not risk to see a OOME.
// See https://github.com/netty/netty/issues/4275
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
callDecode中不断执行抽象decode(ctx, in, out)方法直到in可读数据没有减少或当前handler被remove。
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
while (in.isReadable()) {
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// 检查当前handler是否被remove了
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
decodeRemovalReentryProtection(ctx, in, out);
// 检查当前handler是否被remove了
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
break;
} else {
continue;
}
}
if (oldInputLength == in.readableBytes()) { // 这种情况是解码出了对象但是并没有移动in的readIndex
throw new DecoderException(
StringUtil.simpleClassName(getClass()) +
".decode() did not read anything but decoded a message.");
}
if (isSingleDecode()) {
break;
}
}
} ...
}
fireChannelRead(ctx, msgs, numElements)的处理方式是对每个解码后的消息进行fireChannelRead,交给下一个Handler处理
static void fireChannelRead(ChannelHandlerContext ctx, List<Object> msgs, int numElements) {
if (msgs instanceof CodecOutputList) {
fireChannelRead(ctx, (CodecOutputList) msgs, numElements);
} else {
for (int i = 0; i < numElements; i++) {
ctx.fireChannelRead(msgs.get(i));
}
}
}
static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) {
for (int i = 0; i < numElements; i ++) {
ctx.fireChannelRead(msgs.getUnsafe(i));
}
}
以上就是ByteToMessageDecoder的主要处理部分。关于Netty,面试中会喜欢问道“粘包/拆包”问题,指的是一个消息在网络中是二进制byte流的形式传过去的,接收方如何判断一个消息是否读完、哪里是分割点等,这些可以通过Netty中提供的一些Decoder来实现,例如DelimiterBasedFrameDecoder,FixedLengthFrameDecoder, LengthFieldBasedFrameDecoder。其中最常见的应该是LengthFieldBasedFrameDecoder了,因为自定义的协议中通常会有一个协议头,里面有一个字段描述一个消息的大小长度,然后接收方就能知道消息读到什么时候是读完一个Frame了。这些解码器会在后续的文章中介绍。
自定义Decoder继承ByteToMessageDecoder实现解码的小案例的更多相关文章
- Netty4 自定义Decoder,Encoder进行对象传递
首先我们必须知道Tcp粘包和拆包的,TCP是个“流”协议,所谓流,就是没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的包可能会被拆分 ...
- Java自定义排序:继承Comparable接口,重写compareTo方法(排序规则)
代码: 1 import java.util.*; 2 3 /** 4 * 学习自定义排序:继承Comparable接口,重写compareTo方法(排序规则). 5 * TreeMap容器的Key是 ...
- ES6基础与解构赋值(高颜值弹框小案例!)
let只作用在当前块级作用域内使用let或者const声明的变量,不能再被重新声明let不存在`变量提升` console.log(dad); var dad = '我是爸爸!';//预定义undef ...
- MVC 小案例 -- 信息管理
前几次更新博客都是每次周日晚上到周一,这次是周一晚上开始写,肯定也是有原因的!那就是我的 Tomact 忽然报错,无法启动,错误信息如下!同时我的 win10 也崩了,重启之后连 WIFI 的标志也不 ...
- node.js(小案例)_实现学生信息增删改
一.前言 本节内容主要对小案例做一个总结: 1.如何开始搭建小项目 2.路由设计 3.模块应用 4.项目源码以及实现过程github地址: 项目演示如下: 二.主要内容 1.项目的关键性js源码: 项 ...
- Vue小案例 之 商品管理------学习过滤器 使用过滤器处理日期的格式
代码学习过滤器 过滤器介绍:过滤模型数据,在数据显示前做预处理操作: 内置过滤器:在1.x中,Vue提供了内置过滤器,但是在2.x中已经完全废除: 解决办法: (1)使用第三方库来替代1.x中的内置过 ...
- 一个ssm综合小案例-商品订单管理----写在前面
学习了这么久,一直都是零零散散的,没有把知识串联起来综合运用一番 比如拦截器,全局异常处理,json 交互,RESTful 等,这些常见技术必须要掌握 接下来呢,我就打算通过这么一个综合案例把这段时间 ...
- JSONP跨域访问百度实现搜索提示小案例
一.JSONP简介 JSONP 全称 JSON with padding(填充式 JSON 或参数式 JSON),JSONP实现跨域请求的原理,就是动态创建<script>标签,然后利用& ...
- MyBatis小案例完善增强
https://blog.csdn.net/techbirds_bao/article/details/9233599 上链接为一个不错的Mybatis进阶博客 当你把握时间,时间与你为伍. 将上一个 ...
随机推荐
- ELK原理介绍
本篇转载自:https://www.cnblogs.com/aresxin/p/8035137.html 为什么使用日志系统: 日志系统记录了系统运行.业务处理的方方面面,在故障排除.业务分析.数据挖 ...
- js中获取监听键盘事件
<script type="text/javascript" language=JavaScript charset="UTF-8"> docume ...
- MM-合作伙伴确定过程
第一步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义合作伙伴角色. 第二步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义每个科目组适合的合作伙伴角色. 第三步: ...
- Solon 1.2.13 发布,开启与 Springboot 的互通
Solon 一个类似Springboot的微型开发框架.项目从2018年启动以来,参考过大量前人作品:历时两年,3500多次的commit:内核保持0.1m的身材,超高的Web跑分,良好的使用体验. ...
- 在开发板上显示英文字符和汉字--tiny6410
程序字符需要改成gb2312.否则无法正常显示中文字符. main.c代码: #include <sys/types.h> #include <sys/stat.h> #inc ...
- JVM内存设置多大合适?Xmx和Xmn如何设置?
JVM内存设置多大合适?Xmx和Xmn如何设置? 问题:新上线一个java服务,或者是RPC或者是WEB站点, 内存的设置该怎么设置呢?设置成多大比较合适,既不浪费内存,又不影响性能呢? 分析:依 ...
- java混淆工具 字符串加密 程序加密 代码逻辑混淆 防止反编译
混淆工具使用文档 ht-confusion-project1.0.0 目 录 1.功能介绍... 1 2.安装说明... 3 2.1Window查询jdk版本(点击开始菜单,输入cmd, 输入java ...
- 解决MySQL Workbench导出低版本MySQL时报错Unknown table ‘column_statistics’ in information_schema的问题
在使用高版本MySQL Workbench或MySQL 8.0+版本提供的mysqldump.exe(实际高版本的MySQL Workbench使用的也是高版本的mysqldump.exe)来导出低于 ...
- 买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你最多可以完成 两笔 交易. 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的 ...
- 深入汇编指令理解Java关键字volatile
volatile是什么 volatile关键字是Java提供的一种轻量级同步机制.它能够保证可见性和有序性,但是不能保证原子性 可见性 对于volatile的可见性,先看看这段代码的执行 flag默认 ...