Netty解码器也是非常重要的一个模块, 服务端接收到客户端发送过来的消息, 准确说是字节数组, Netty底层已经将它们读取成ByteBuf了, 但是这些ByteBuf是没有任何含义的,需要我们根据业务来对字节数组进行解码。本文中我们将介绍Netty中常见的两种解码器DelimiterBasedFrameDecoder和FixedLengthFrameDecoder。

Netty解码器

  • 1. 前言
    • 1.1 Netty解码器
    • 1.2 ByteToMessageDecoder
  • 2. DelimiterBasedFrameDecoder解码器应用
    • 2.1 服务端代码
    • 2.2 客户端代码
    • 2.3 运行结果
  • 3. FixedLengthFrameDecoder解码器应用
    • 3.1 服务端代码
    • 3.2 telnet客户端测试

1. 前言

TCP以流的方式进行传输数据,上层的应用协议为了对消息进行区分,通常会采用以下4种方式:

  • 消息长度固定:累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;然后会将计数器重置,重新开始读取下一个数据报;
  • 回车换行符作为消息结束符:以回车换行符来标记一个数据报j结束;
  • 特殊字符标识:自定义一个特殊字符,来作为数据报结束的标识;
  • 消息头定义:通过在消息头种定义长度字段来标识消息的总长度。

Netty对上述4种应用做了统一的抽象,提供了4种解码器来解决相应的问题。在本文中我们将详细介绍DelimiterBasedFrameDecoder和FixedLengthFrameDecoder两种解码器,两者分别可以完成上述第3和第1种功能。

1.1 Netty解码器

在对这两种解码器介绍之前,我们简单了解一下Netty解码器的工作原理。下面首先给出一个简单的示例:

上述图片种展示是一个ToIntegerDecoder解码器的工作过程,从字面上我们可以了解,该解码器是将一个字节数组转化为Integer类型数据。decoder 负责将“入站”数据从一种格式转换到另一种格式,Netty的解码器是一种 ChannelInboundHandler 的抽象实现。实践中使用解码器很简单,就是将入站数据转换格式后传递到 ChannelPipeline 中的下一个ChannelInboundHandler 进行处理;这样的处理是很灵活的,我们可以将解码器放在 ChannelPipeline 中,重用逻辑。

Netty 提供了丰富的解码器抽象基类,我们可以很容易的实现这些基类来自定义解码器。主要分两类:

  • 解码字节到消息(ByteToMessageDecoder 和 ReplayingDecoder)
  • 解码消息到消息(MessageToMessageDecoder)

由于常用的几种解码器都是解码字节到消息,那么下面简单了解ByteToMessageDecoder。

1.2 ByteToMessageDecoder

ByteToMessageDecoder 是用于将字节转为消息(或其他字节序列)。你不能确定远端是否会一次发送完一个完整的“信息”,因此这个类会缓存入站的数据,直到准备好了用于处理。ByteToMessageDecoder抽象类中有两个最重要的方法,如下表所示:

方法名称 描述
Decode 需要实现的唯一抽象方法。 通过具有输入字节的ByteBuf和添加了已解码消息的List来调用它。 反复调用decode(),直到列表返回时为空。 然后将List的内容传递到管道中的下一个处理程序。
decodeLast 所提供的默认实现只调用了decode()。当Channel变为非活动状态时,此方法被调用一次。

下面我们依旧使用上面的ToIntegerDecoder作为示例。假设我们接收一个包含简单整数的字节流,每个都单独处理。在本例中,我们将从入站 ByteBuf 读取每个整数并将其传递给 pipeline 中的下一个ChannelInboundHandler。“解码”字节流成整数我们将扩展ByteToMessageDecoder,实现类为“ToIntegerDecoder”,如下图所示。

每次从入站的 ByteBuf 读取四个字节,解码成整形,并添加到一个 List (本例是指 Integer),当不能再添加数据到 list 时,它所包含的内容就会被发送到下个 ChannelInboundHandler

读者如果想要详细了解解码器的源码设计可以阅读博客。

2. DelimiterBasedFrameDecoder解码器应用

DelimiterBasedFrameDecoder解码器是通用的分隔符解码器,可支持多个分隔符,每个分隔符可为一个或多个字符。如果定义了多个分隔符,并且可解码出多个消息帧,则选择产生最小帧长的结果。下面我们使用$_作为分隔符来演示。

2.1 服务端代码

package netty.frame.delimiter;import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/
public class EchoServer {public void bind(int port) throws Exception {// 配置服务端的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, 100).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoServerHandler());}});// 绑定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoServer().bind(port);}
}

在initChannel方法中,我们首先创建分隔符缓冲对象ByteBuf,然后使用该分隔符缓冲对象创建DelimiterBasedFrameDecoder编码器对象,并加入到ChannelPipeline中。其中第二个参数白哦是消息的最大长度,如果超过这个长度还没有找到分隔符,则认为消息出错,报出异常,这是为了防止异常数据导致内存溢出,提高编码器的可靠性,最后还添加了字符串解码器和服务端处理类实例。

package netty.frame.delimiter;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*** created by LMR on 2020/5/20*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {int counter = 0;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {String body = (String) msg;System.out.println("This is " + ++counter + " times receive client : ["+ body + "]");body += "$_";ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());ctx.writeAndFlush(echo);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();// 发生异常,关闭链路}
}

channelRead方法非常简单,由于我们在ChannelPipeline中添加了多个编码器,那个在这里接收到的消息直接就是完整的消息数据字符串,由于我们使用DelimiterBasedFrameDecoder解码器过滤掉了分隔符,在这里我们重新添加分隔符,以便于客户端识别,再发送给客户端。

2.2 客户端代码

package netty.frame.delimiter;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/
public class EchoClient {public void connect(int port, String host) throws Exception {// 配置客户端NIO线程组EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoClientHandler());}});// 发起异步连接操作ChannelFuture f = b.connect(host, port).sync();// 当代客户端链路关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放NIO线程组group.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoClient().connect(port, "127.0.0.1");}
}

同样在客户端我们也添加相应的解码器,然后创建客户端处理类对象EchoClientHandler。

package netty.frame.delimiter;import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;/*** created by LMR on 2020/5/20*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {private int counter;static final String ECHO_REQ = "This is a example by LMRZero.$_";@Overridepublic void channelActive(ChannelHandlerContext ctx) {for (int i = 0; i < 10; i++) {ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));}}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {System.out.println("This is " + ++counter + " times receive server : ["+ msg + "]");}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

2.3 运行结果

服务端结果:

客户端结果:

3. FixedLengthFrameDecoder解码器应用

FixedLengthFrameDecoder是按照固定长度frameLength解码出消息帧。在本节中我们使用一个应用实例对其用法进行介绍。

3.1 服务端代码

我们在服务端ChannelPipeline中添加FixedLengthFrameDecoder,设置其长度为20,然后同样添加字符串解码器和服务端处理类实例。

package netty.frame.fixedLen;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/
public class EchoServer {public void bind(int port) throws Exception {// 配置服务端的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, 100).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ch.pipeline().addLast(new FixedLengthFrameDecoder(20));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoServerHandler());}});// 绑定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoServer().bind(port);}
}

下面看看服务端处理类EchoServerHandler的实现代码:

package netty.frame.fixedLen;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*** created by LMR on 2020/5/20*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {System.out.println("Receive client : [" + msg + "]");}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();// 发生异常,关闭链路}
}

在本例中,我们在服务端接收到消息之后直接进行打印,不进行任何的操作。利用FixedLengthFrameDecoder解码器,无论一次接收到多少数据报,都会按照固定长度来进行解码。下面我们通过telnet命令行来测试服务端能否按照预期进行工作。

3.2 telnet客户端测试

在这里我们通过telnet命令来对服务端进行测试,下面介绍具体步骤:
(1)启动服务端
(2)开启本地回显功能,便于观察

(3)打开命令行窗口,输入telnet localhost 8080

(3)在命令行窗口输入需要传输的数据内容:


(4)服务端查看结果

可以看出每次都是收到20个字节的数据。

————————————————————————————————————————
参考博客和书籍:
https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-x7mn28bx.html
https://blog.csdn.net/usagoole/article/details/87389182
https://www.cnblogs.com/yuanrw/p/9866356.html
https://blog.csdn.net/mascf/article/details/60478539
https://www.cnblogs.com/ZhuChangwu/p/11225158.html
《Netty 权威指南》

如果喜欢的话希望点赞收藏,关注我,将不间断更新博客。

希望热爱技术的小伙伴私聊,一起学习进步

来自于热爱编程的小白

你了解Netty的编解码器吗?史上最通俗易懂的Netty解码器应用案例带你解开Netty解码器的神秘面纱相关推荐

  1. Netty面试题(史上最全)

    文章很长5万字,而且不断更新,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1 ...

  2. 史上最详细Docker安装最新版Minio 带详解 绝对值得收藏!!! 让我们一起学会使用minio搭建属于自己的文件服务器!!走上白嫖之路!解决启动了但是浏览器访问不了的原因

    让我们一起学会使用minio搭建属于自己的文件服务器!!走上白嫖之路! WARNING: Console endpoint is listening on a dynamic port (34451) ...

  3. 【ASM】史上最通俗易懂的ASM教程 ASM 插件

    1.概述 转载:史上最通俗易懂的ASM教程 一勺思想 We are all in the gutter, but some of us are looking at the stars. (我们都生活 ...

  4. 史上最通俗易懂的CPU知识!一分钟秒懂主频、核心、线程、架构!

    史上最通俗易懂的CPU知识!一分钟秒懂主频.核心.线程.架构! 我们都说CPU相当于人类的大脑,在日常生活中,人脑是术业有专攻,有人天生适合搞艺术,有人天生适合搞科学.CPU作为计算机的大脑,其实也是 ...

  5. NeurIPS 2020放榜,接收率史上最低!AC:低接收率带不来有趣的论文

    ↑ 点击蓝字 关注视学算法 作者丨魔王 来源丨机器之心 编辑丨极市平台 前几日,NeurIPS 2020 会议发送论文录取通知. 根据杜克大学陈怡然教授的微博信息,NeurIPS 2020 共收到论文 ...

  6. 史上最通俗易懂 pvalue

    各种富集分析总是用到pvalue,而我的概率论知识和术语快忘完了,所以只能用小学生都能理解的方式和例子来重新缕一遍pvalue. 承接上一篇文章:史上最通俗 Gene enrichment analy ...

  7. 转载:史上最全|阿里那些牛逼带闪电的开源工具,你知道几个?

    开源展示了人类共同协作,成果分享的魅力,每一次技术发展都是站在巨人的肩膀上,技术诸多创新和发展往往就是基于开源发展起来的,没有任何一家网络公司可以不使用开源技术,仅靠自身技术而发展起来.阿里巴巴各个团 ...

  8. 史上最详细的RocketMq 下单支付案例 分享

    1. 案例介绍 1.1 业务分析 模拟电商网站购物场景中的[下单]和[支付]业务 1)下单 用户请求订单系统下单 订单系统通过RPC调用订单服务下单 订单服务调用优惠券服务,扣减优惠券 订单服务调用调 ...

  9. 史上最通俗易懂的IPFS入门介绍:01

    主编丨ipfs中国社区:ip君 来源丨www.ipfs.cn中国社区 作为刚刚接触ipfs的你们,一定一脸懵逼,心中简直是万马奔腾,尼玛,这是什么东西? 没关系,IP君特意为了你们,编写了一份IPFS ...

最新文章

  1. Linux给命令设置超时时间,Linux命令技巧和时间设置
  2. ESXI主机定时重启脚本
  3. HTTP深入浅出个人总结
  4. 基于UNet和camvid数据集的道路分割
  5. 企业服务总线全双工异步通信机
  6. [Abp 源码分析]后台作业与后台工作者
  7. [html] html的元素有哪些(包含H5)?
  8. 周鸿祎在美参加的信息安全大会已有两人确诊新冠肺炎
  9. 告诉刚入行的兄弟们,钱是这么赚的!
  10. POJ-2031-Building a Space Station
  11. 【原创】pads2007 Layout 电气连接性检查过孔显示错误
  12. 高二获全奖跳级进哈佛,29岁坐拥数亿身家,这位曾让马云畏惧的“天才神童”现在怎么样了?...
  13. 全球第一博客---缠中说禅
  14. python对比excel重复数据_python入门之对比两份excel表格数据
  15. python构建电商用户画像(1)
  16. 网络工程师——交换技术(线路交换、分组交换技术、帧中继交换、信元交换技术)
  17. 字节跳动(今日头条)小程序支付、支付宝、微信支付完整版
  18. Python 中iterator
  19. java buildpack是什么_javabuildpack改造
  20. Mac下安装与使用Medis

热门文章

  1. 转帖: 80分钟打造娱乐型ubuntu7.10
  2. U盘无法访问,文件或目录损坏无法读取
  3. 推荐系统遇上深度学习(二十六)--知识图谱与推荐系统结合之DKN模型原理及实现
  4. html ul li 横排居中排列
  5. AR眼镜单、双目选哪个好?技术工程师资深经验分享!虹科干货
  6. 30款精美的国外企业网站模板 PSD 免费下载
  7. mybatis mysql cursor_使用cursor事务
  8. 联想互联网诊疗与AI医学影像生态解决方案——提供数据连续性的医疗保健和生命科学解决方案
  9. 基于计组实验软件CMStudio设计一种简单同或运算指令系统
  10. TP-LINK路由器的QSS/WPS连接软件