什么是编解码器?

在网络中都是以字节码的数据形式来传输数据的,如何将其和目标应用程序的自定义消息对象数据格式进行相互转换。这种转换逻辑就需要编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将字节流从一种格式转换为另一种格式。

一、Netty编解码器

Netty里面的编解码器(入站解码,出站编码):

  • 解码器:将消息从字节或其他序列形式转换成指定的消息对象。即负责处理“入站 InboundHandler”数据。
  • 编码器:将消息对象转换成字节或其他序列形式在网络上传输。即负责“出站 OutboundHandler” 数据。

Netty 的编解码器实现了 ·ChannelHandlerAdapter·,也是一种特殊的 ChannelHandler,所以,依赖于 hannelPipeline,可以将多个编解码器链接在一起,以实现复杂的转换逻辑。

服务器编码数据后发送到客户端,客户端需要对数据进行解码。
由于是双向通信,因此,在服务端和客户端的中均需要添加编解码器。

1、解码器

1.1 解码器

解码器:将消息从字节或其他序列形式转换成指定的消息对象。

解码器负责处理“入站 InboundHandler”数据。 解码器实现了 ChannelInboundHandler,需要将解码器放在 ChannelPipeline中。

Netty中主要提供了两个解码器抽象基类:

  • ByteToMessageDecoder: 用于将字节转为消息,需要检查缓冲区是否有足够的字节

    • ReplayingDecoder: 继承 ByteToMessageDecoder,不需要检查缓冲区是否有足够的字节,但是 ReplayingDecoder速度略慢于ByteToMessageDecoder,同时不是所有的 ByteBuf都支持。 项目复杂性高则使用 ReplayingDecoder,否则使用ByteToMessageDecoder
  • MessageToMessageDecoder: 用于从一种消息解码为另外一种消息

核心方法: decode()方法是必须实现的唯一抽象方法。

decode(ChannelHandlerContext ctx, ByteBuf msg, List out)

1.2 抽象类ByteToMessageDecoder

ByteToMessageDecoder:将字节解码为消息(或者另一个字节序列)。

由于我们不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲,需要我们检查缓冲区是否有足够的字节,直到它准备好处理。

decode()方法被调用时将会传入一个包含了传入数据的 ByteBuf,以及一个用来添加解码消息的 List。
对这个方法的调用将会重复进行,直到确定没有新的元素被添加到该 List,或者该 ByteBuf 中没有更多可读取的字节时为止。然后,如果该 List 不为空,那么它的内容将会被传递给 ChannelPipeline 中的下一个ChannelInboundHandler。

TooLongFrameException:

由于 Netty 是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们。因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。

为了解除这个常见的顾虑,Netty 提供了 TooLongFrameException 类,其将由解码器在帧超出指定的大小限制时抛出。

为了避免这种情况,你可以设置一个最大字节数的阈值,如果超出该阈值,则会导致抛出一个 TooLongFrameException(随后会被 ChannelHandler.exceptionCaught()方法捕获)。然后,如何处理该异常则完全取决于该解码器的用户。某些协议(如 HTTP)可能允许你返回一个特殊的响应。而在其他的情况下,唯一的选择可能就是关闭对应的连接。

1.3 抽象类MessageToMessageDecoder

MessageToMessageDecoder:将一种消息类型解码为另一种消息。

使用该抽象基类可以使消息在两个消息格式之间进行转换。例如,从 String->Integer,POJO到POJO等。

该抽象基类的完整声明:

public abstract class MessageToMessageDecoder extends ChannelInboundHandlerAdapter

  • MessageToMessageDecoder,T 代表源数据的类型

2、编码器

2.1 编码器

编码器:将消息对象转换成字节或其他序列形式在网络上传输。

编码器负责处理“出站 OutboundHandler” 数据。 编码器实现了 ChannelOutboundHandler,需要将解码器放在 ChannelPipeline中。它和上面的解码器的功能正好相反。

Netty中主要提供了两个编码器抽象基类:

  • MessageToByteEncoder:将消息编码为字节
  • MessageToMessageEncoder,:将消息编码为消息,T 代表源数据的类型

核心方法:encode()方法是你需要实现的唯一抽象方法。

2.2 抽象类MessageToByteEncoder

MessageToByteEncoder:将消息编码为字节

decode()方法被调用时将会传入要被该类编码为 ByteBuf 的出站消息(类型为 I 的)。该 ByteBuf 随后将会被转发给 ChannelPipeline 中的下一个 ChannelOutboundHandler

这个类只有一个方法,而解码器有两个。
原因是解码器通常需要在 Channel 关闭之后产生最后一个消息(因此也就有了 decodeLast()方法。这显然不适用于编码器的场景(在连接被关闭之后仍然产生一个消息是毫无意义的)。

2.3 抽象类MessageToMessageEncoder

MessageToMessageEncoder:将消息编码为消息

每个通过 write()方法写入的消息都将会被传递给 encode()方法,以编码为一个或者多个出站消息。随后,这些出站消息将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。

3、编解码器类

上面将解码器和编码器作为单独的实体了解,但是有时在同一个类中管理入站和出站数据和消息的转换是很有用的。Netty 的抽象编解码器类正好用于这个目的,因为它们每个都将捆绑一个解码器/编码器对。这些类同时实现了 ChannelInboundHandler 和 ChannelOutboundHandler 接口。


相关的类:

  • 抽象类 ByteToMessageCodec
  • 抽象类 MessageToMessageCodec

3.1 抽象类ByteToMessageCodec

通过使用 ByteToMessageCodec ,我们可以在单个的类中实现将字节转为消息的往返过程。

ByteToMessageCodec 是一个参数化的类,定义如下:

public abstract class ByteToMessageCodec extends ChannelDuplexHandler

3.2 抽象类MessageToMessageCodec

通过使用 MessageToMessageCodec,我们可以在单个的类中实现将消息转为消息的往返过程。

MessageToMessageCodec 是一个参数化的类,定义如下:

public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN> extends ChannelDuplexHandler

三、解码器/编码器示例

1、MessageToMessage

示例:将消息转为消息的往返过程。String -> String。通过单独的解码器/编码器实现。

1)解码器

public class StringMessageDecoder extends MessageToMessageDecoder<ByteBuf> {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {System.out.println("StringMessageDecoder 消息正在进行解码...");//将 ByteBuf转换为 String,传递到下一个handlerout.add(msg.toString(CharsetUtil.UTF_8));}}

2)编码器

public class StringMessageEncoder extends MessageToMessageEncoder<String> {@Overrideprotected void encode(ChannelHandlerContext ctx, String msg, List out) throws Exception {System.out.println("StringMessageEncoder 消息正在进行编码....");//将 String转换为 ByteBuf,传递到下一个handlerout.add(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));}
}

3)客户端Handler

public class MyStringClientHandler extends SimpleChannelInboundHandler {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//发送消息到服务端ChannelFuture future = ctx.writeAndFlush("Hello,我是 Netty客户端,发送 String类型数据。");future.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (future.isSuccess()) {System.out.println("数据发送成功!");} else {System.out.println("数据发送失败!");}}});}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {//接收服务端发送过来的消息//因为我们使用了 String 编解码器,所以不会是 ByteBuf,字节信息System.out.println("Client Accept Server("+ ctx.channel().remoteAddress() +")消息 ->" + msg);}}

4)服务端Handler

public class MyStringServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("MyServerHandler 连接已建立...");super.channelActive(ctx);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//获取客户端发送过来的消息System.out.println("Server Accept Client Context ("+ ctx.channel().remoteAddress() +")消息 ->" + msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//发送消息给客户端//ByteBuf byteBuf = Unpooled.copiedBuffer("Server Receive Client msg", CharsetUtil.UTF_8);//ctx.writeAndFlush(byteBuf);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//发生异常,关闭通道//cause.printStackTrace();ctx.close();}
}

5)将解码器/编码器添加到 ChannelPipeline中

因为是双向通信,因此,在服务端和客户端的pipeline中均需要添加编解码器。

 private static class StringChannelInitializerImpl extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {// 添加客户端通道的处理器socketChannel.pipeline()//添加解码器和编码器.addLast("stringMessageDecoder", new StringMessageDecoder()).addLast("stringMessageEncoder", new StringMessageEncoder()).addLast(new MyStringClientHandler());}}

6)测试

先启动服务端,再启动客户端,结果如下:

四、编解码器类示例

1、MessageToMessage

示例:将消息转为消息的往返过程。String -> String。通过单个的编解码器类实现。

将上面单独的解码器/编码器实现改造一下搞定。

1)编解码器类

/*** String 编码解码器, 出入站相对于服务端和客户端时相对的<br/>* 继承 MessageToMessageCodec<ByteBuf, String> 时,可以加泛型 <br/>**/
public class StringMessageCodec extends MessageToMessageCodec{/*** 编码* @param ctx* @param msg* @param out* @throws Exception*/@Overrideprotected void encode(ChannelHandlerContext ctx, Object msg, List out) throws Exception {System.out.println("StringMessageCodec 消息正在进行编码...");String str = (String) msg;out.add(Unpooled.copiedBuffer(str, CharsetUtil.UTF_8));//传递到下一个handler}/*** 解码* @param ctx* @param msg* @param out* @throws Exception*/@Overrideprotected void decode(ChannelHandlerContext ctx, Object msg, List out) throws Exception {System.out.println("StringMessageCodec 正在进行消息解码...");ByteBuf byteBuf = (ByteBuf) msg;out.add(byteBuf.toString(CharsetUtil.UTF_8));//传递到下一个handler}//@Override//protected void encode(ChannelHandlerContext channelHandlerContext, String msg, List<Object> out) throws Exception {//    out.add(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));//传递到下一个handler//}////@Override//protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> out) throws Exception {//    out.add(byteBuf.toString(CharsetUtil.UTF_8));//传递到下一个handler//}}

2)将编解码器类添加到 ChannelPipeline中

socketChannel.pipeline()//添加解码器和编码器              .addLast(new StringMessageCodec()).addLast(new MyStringClientHandler());

handler同上,测试结果也同上。

– 求知若饥,虚心若愚。

Netty 编解码器详解相关推荐

  1. Netty原理详解系列(一)---NIO中的BufferChanel

    文章目录 1.概述 2.缓冲区Buffer定义 3.Buffer内部结构 4.Buffer常用的操作 4.1 allocate 4.2 wrap 4.3 put 4.4 flip 4.5 get 4. ...

  2. Netty详解(五):Netty TCP粘包 拆包

    1. 概述 无论是服务端还是客户端,我们读取或者发送消息的时候,都需要考虑TCP底层的粘包和拆包机制.下面我们来通过Netty来详解TCP底层的粘包和拆包机制. 2. TCP底层的粘包和拆包机制 TC ...

  3. Netty详解(七):Netty 编解码以及消息头编解码器

    1. MessagePack 概述 MessagePack是一个高效的二进制序列化框架,像JSON一样支持不同语言间的数据交换,速度更快,序列化之后的码流更小. MessagePacke优点 编解码高 ...

  4. Netty详解(持续更新中)

    Netty详解 1. Netty概述 1.1 Netty简介 1.2 原生NIO问题 1.3 Netty特点 1.4 Netty应用场景 1.3 Netty版本说明 2. Java IO模型 2.1 ...

  5. netty 之 telnet HelloWorld 详解

    2019独角兽企业重金招聘Python工程师标准>>> 依赖工具 Maven Git JDK IntelliJ IDEA 源码拉取 从官方仓库 https://github.com/ ...

  6. netty系列之:netty中各不同种类的channel详解

    文章目录 简介 ServerChannel和它的类型 Epoll和Kqueue AbstractServerChannel ServerSocketChannel ServerDomainSocket ...

  7. netty系列之:netty中的Channel详解

    文章目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的 ...

  8. netty系列之:netty中的ByteBuf详解

    文章目录 简介 ByteBuf详解 创建一个Buff 随机访问Buff 序列读写 搜索 其他衍生buffer方法 和现有JDK类型的转换 总结 简介 netty中用于进行信息承载和交流的类叫做Byte ...

  9. 抓到Netty一个隐藏很深的内存泄露Bug | 详解Recycler对象池的精妙设计与实现

    本系列Netty源码解析文章基于 4.1.56.Final版本 最近在 Review Netty 代码的时候,不小心用我的肉眼抓到了一个隐藏很深很深的内存泄露 Bug. 于是笔者将这个故事-哦不 -事 ...

最新文章

  1. ASP.NET MVC 异常Exception拦截
  2. 【测试】批量删除供应商配额(Quota )
  3. 比较两个日期大小和获取当前月最大天数的存储过程
  4. 探索发现:平台云——云的新风向
  5. 技术选型:Sentinel vs Hystrix
  6. Qt笔记-QSslSocket双向认证
  7. java程序员必备基础知识
  8. 春Phone计划 51cto沙龙上海站
  9. create symbolic array
  10. sca60c使用程序_使用PHP的SDO和SCA扩展
  11. ubuntu16.04修改鼠标按键功能
  12. 51单片机流水灯实验
  13. 自己做的一个漫画下载器
  14. PS图片的两种大小及修改,psd源码文件,图层概念
  15. 拼图游戏代码html5,翻译的HTML5拼图游戏(附源码)
  16. 微信计步器怎么不计步_微信运动不计步数是怎么回事?
  17. ESP8266-01实战一——带OLED显示屏电子时钟
  18. Java并发编程之CyclicBarrier和CountDownLatch
  19. sql中将字符串转换为decimal
  20. Coderwars使用

热门文章

  1. mac mysql中文乱码问题(亲测有效)
  2. 悟道 冥 与 力 万事皆可成
  3. 微博舆情 之 数据获取
  4. 爬虫—爬取微博热搜榜
  5. 【论文翻译】Transferring GANs: generating images from limited data
  6. java中record,Java 中的 record 关键字
  7. php源生代码是什么,php源生分页代码+join连接多表
  8. springboot 简单的扫码登录 demo
  9. shell - 01 - Shell入门:扎好马步 走的更稳
  10. oracle请求输出全部都是fndwrr,oracle ebs系统维护技巧汇总