首页 > 其他分享 >Netty实战(十)

Netty实战(十)

时间:2023-06-02 14:38:35浏览次数:32  
标签:实战 Netty 字节 List 解码器 消息 ByteBuf out

(编解码器框架)

一、什么是编解码器框架

网络只将数据看作是原始的字节序列。但我们的应用程序则会把这些字节组织成有意义的信息。在数据和网络字节流之间做相互转换是最常见的编程任务之一。例如,我们可能需要处理标准的格式或者协议(如 FTP 或 Telnet)、实现一种由第三方定义的专有二进制协议,或者扩展一种由自己的组织创建的遗留的消息格式。将应用程序的数据转换为网络格式,以及将网络格式转换为应用程序的数据的组件分别叫作编码器和解码器,同时具有这两种功能的单一组件叫作编解码器。

Netty 提供了一系列用来创建所有这些编码器、解码器以及编解码器的工具,从专门为知名协议(如 HTTP以及 Base64)预构建的类,到你可以按需定制的通用的消息转换编解码器,应有尽有。

1.1 解码器

上面我们说了将网络格式转换为应用程序的数据的组件分别叫作编码器和解码器,现在我们来说说解处理入站数据的-解码器。

Netty提供的解码器类有两个类型:

  • 将字节解码为消息——ByteToMessageDecoder 和 ReplayingDecoder;
  • 将一种消息类型解码为另一种——MessageToMessageDecoder。

因为解码器是负责将入站数据从一种格式转换到另一种格式的,所以哪怕 Netty 的解码器实现了 ChannelInboundHandler 也很正常。

什么时候会用到解码器呢?

每当需要为 ChannelPipeline 中的下一个 ChannelInboundHandler 转换入站数据时会用到。此外,得益于 ChannelPipeline 的设计,可以将多个解码器链接在一起,以实现任意复杂的转换逻辑。

1.1.1 抽象类 ByteToMessageDecoder

将字节解码为消息(或者另一个字节序列)是最常见的,Netty 为它提供了一个抽象的基类:ByteToMessageDecoder。

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

ByteToMessageDecoder这个类提供了两个方法:

1、decode(ChannelHandlerContext ctx,ByteBuf in,List<Object> out)

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

2、decodeLast(ChannelHandlerContext ctx,ByteBuf in,List<Object> out)

这个默认只是简单地调用了decode()方法。当Channel的状态变为非活动时,这个方法将会被调用一次。可以重写该方法以提供特殊的处理。

举个例子:

假设你接收了一个包含简单 int 的字节流,每个 int都需要被单独处理。在这种情况下,你需要从入站 ByteBuf 中读取每个 int,并将它传递给ChannelPipeline 中的下一个 ChannelInboundHandler。为了解码这个字节流,你要扩展ByteToMessageDecoder 类。(需要注意的是,原子类型的 int 在被添加到 List 中时,会被自动装箱为 Integer。)

//扩展 ByteToMessageDecoder 类,以将字节解码为特定的格式
public class ToIntegerDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception{
//检查是否至少有 4字节可读(一个 int的字节长度)
if (in.readableBytes() >= 4) {
//从入站 ByteBuf 中读取一个 int,并将其添加到解码消息的 List 中
out.add(in.readInt());
}
}
}

编解码器中的引用计数 之前提过引用计数需要特别的注意。对于编码器和解码器来说,其过程也是相当的简单:一旦消息被编码或者解码,它就会被 ReferenceCountUtil.release(message)调用自动释放。如果你需要保留引用以便稍后使用,那么你可以调用 ReferenceCountUtil.retain(message)方法。这将会增加该引用计数,从而防止该消息被释放。

1.1.2 抽象类 ReplayingDecoder

ReplayingDecoder扩展了ByteToMessageDecoder类,使得我们不必调用 readableBytes()方法。它通过使用一个自定义的ByteBuf实现 ,ReplayingDecoderByteBuf,包装传入的ByteBuf实现了这一点,其将在内部执行该调用 。

这个类的完整声明是:

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

类型参数 S 指定了用于状态管理的类型,其中 Void 代表不需要状态管理。

同样是上面那个例子,用这个类来写就变得简单一些:

//扩展 ReplayingDecoder<Void>以将字节解码为消息
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {
@Override
//传入的 ByteBuf 是 ReplayingDecoderByteBuf
public void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception{
//从入站 ByteBuf 中读取一个 int,并将其添加到解码消息的 List 中
out.add(in.readInt());
}
}

那么这些类什么时侯使用?该使用哪个? 这里有一个简单的准则:如果使用 ByteToMessageDecoder不会引入太多的复杂性,那么请使用它;否则,请使用 ReplayingDecoder。

1.1.3 抽象类 MessageToMessageDecoder

MessageToMessageDecoder主要用于在两个消息格式之间进行转换,例如:一种 POJO 类型转换为另一种。

它的完整声明是这样:

public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter

类型参数 I 指定了 decode()方法的输入参数 msg 的类型,是必须实现的唯一方法。

这个类包括一个 decode(ChannelHandlerContext ctx,I msg,List<Object> out) 方法,对于每个需要被解码为另一种格式的入站消息来说,这个方法都将会被调用。解码消息随后会被传递给 ChannelPipeline中的下一个ChannelInboundHandler。

举个例子:

我们编写一个 IntegerToStringDecoder 解码器来扩展 MessageToMessageDecoder<Integer>。使用decode()方法会把 Integer 参数转换为String后再添加到传出的List中,并转发给下一个ChannelInboundHandler。

public class IntegerToStringDecoder extends
MessageToMessageDecoder<Integer> {
@Override
public void decode(ChannelHandlerContext ctx, Integer msg,List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}

1.1.4 TooLongFrameException 类

Netty是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们。因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解除这个常见的顾虑,Netty提供了TooLongFrameException 类,其将由解码器在帧超出指定的大小限制时抛出。

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

1.2 编码器

编码器实现了 ChannelOutboundHandler,并将出站数据从一种格式转换为另一种格式。和解码器正好相反。

编码器也有两种类型:

  • 将消息编码为字节;
  • 将消息编码为消息

1.2.1 抽象类 MessageToByteEncoder

MessageToByteEncoder是ByteToMessageDecoder的逆向,它提供了一个方法:

encode(ChannelHandlerContext ctx,I msg,ByteBuf out)

这个encode()方法是需要实现的唯一抽象方法。它被调用时将会传入要被该类编码为 ByteBuf 的(类型为 I 的)出站消息。该 ByteBuf 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。

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

1.2.2 抽象类 MessageToMessageEncoder

和解码器类似, MessageToMessageEncode类是将出站数据将从一种消息编码为另一种。它同样有一个:encode( ChannelHandlerContext ctx, I msg, List<Object> out) 方法。

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

二、抽象的编解码器类

虽然我们一直将解码器和编码器作为单独的实体讨论,但大部分情况大家都是在同一个类中管理入站和出站数据和消息的转换。Netty的抽象编解码器类每个都捆绑一个解码器/编码器对,以处理我们一直在学习的这两种类型的操作。这些类同时实现了ChannelInboundHandler 和 ChannelOutboundHandler 接口。

2.1 抽象类 ByteToMessageCodec

假设我们需要将字节解码为某种形式的消息,可能是 POJO,随后再次对它进行编码。那么就要用到ByteToMessageCodec,它结合了ByteToMessageDecoder 以及它的逆向——MessageToByteEncoder。

任何的请求/响应协议都可以作为使用ByteToMessageCodec的理想选择。例如,在某个SMTP的实现中,编解码器将读取传入字节,并将它们解码为一个自定义的消息类型,如SmtpRequest。而在接收端,当一个响应被创建时,将会产生一个SmtpResponse,其将被编码回字节以便进行传输。

ByteToMessageCodec提供了3个方法:

1、decode

decode(ChannelHandlerContext ctx,ByteBuf in,List<Object>)

只要有字节可以被消费,这个方法就将会被调用。它将入站ByteBuf 转换为指定的消息格式,并将其转发给ChannelPipeline 中的下一个 ChannelInboundHandler.

2、decodeLast

decodeLast(ChannelHandlerContext ctx,ByteBuf in,List<Object> out

这个方法的默认实现委托给了 decode()方法。它只会在Channel 的状态变为非活动时被调用一次。它可以被重写以实现特殊的处理

3、encode

encode(ChannelHandlerContext ctx,I msg,ByteBuf out)

对于每个将被编码并写入出站 ByteBuf 的(类型为 I 的)消息来说,这个方法都将会被调用。

2.2 抽象类 MessageToMessageCodec

MessageToMessageCodec可以帮助我们在一个类中实现以将一种消息格式转换为另外一种消息格式这种转换的往返过程,它是一个参数化的类。

MessageToMessageCodec定义如下:

public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>

它提供了两种方法:

第一种:

protected abstract decode(ChannelHandlerContext ctx,INBOUND_IN msg,List<Object> out)

这个方法被调用时会被传入 INBOUND_IN 类型的消息。它将把它们解码为 OUTBOUND_IN 类型的消息,这些消息将被转发给 ChannelPipeline 中的下一个 ChannelInboundHandler

第二种:

protected abstract encode(ChannelHandlerContext ctx,OUTBOUND_IN msg,List<Object> out)

对于每个 OUTBOUND_IN 类型的消息,这个方法都将会被调用。这些消息将会被编码为 INBOUND_IN 类型的消息,然后被转发给 ChannelPipeline 中的下一个ChannelOutboundHandler。

decode()方法是将INBOUND_IN类型的消息转换为OUTBOUND_IN类型的消息,而encode()方法则进行它的逆向操作。将INBOUND_IN类型的消息看作是通过网络发送的类型,而将OUTBOUND_IN类型的消息看作是应用程序所处理的类型。

2.3 CombinedChannelDuplexHandler 类

前面提到大家大多数情况下会将编码器和解码器放在同一个类中进行管理,这样便于管理的同时也会带来一个问题。那就是这样的类重用性不高,那么有没有一种既有高可重用性,又不会牺牲将一个解码器和一个编码器作为一个单独的单元部署所带来的便利性的方案呢?

答案就是:CombinedChannelDuplexHandler 。

public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler,O extends ChannelOutboundHandler>

这个类充当了 ChannelInboundHandler 和 ChannelOutboundHandler(该类的类型参数 I 和 O)的容器。通过提供分别继承了解码器类和编码器类的类型,我们可以实现一个编解码器,而又不必直接扩展抽象的编解码器类。

标签:实战,Netty,字节,List,解码器,消息,ByteBuf,out
From: https://blog.51cto.com/TiMi/6402173

相关文章

  • 新星计划|项目实训|SSM旅游网项目实战笔记一
    应邀请,特委派公司开发负责人张老师带队新星计划:SSM旅游网项目实训。现将实训的相关笔记分期发放,以供参考。如需要相关资料,可以博客尾部添加微信获取。一、实训介绍实训目的:其实通过实际的项目来检验大家的理论水平和实操水平,并同时通过实际的项目来积相应的项目经验。IT行业:主要特......
  • OpenMMLab AI实战营第二期
    OpenMMLabAI实战营笔记OpenMMLab简介OpenMMLab概述:中国人工智能计算机视觉算法体系每一种计算机视觉任务对应OpenMMLab的一个算法库内容:视觉基础库:MMCV,MMEngine算法框架:MMPretrain,MMDetection,MMDetection3D,...常用算法库:MMDetection(最有影响力的算法库......
  • JS逆向实战16——猿人学第20题 新年挑战-wasm进阶
    声明本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!网站https://match.yuanrenxue.cn/match/20网站分析首先进去网站,我们查看下接口发现有两个值是改变......
  • 分布式队列编程:模型、实战
    介绍作为一种基础的抽象数据结构,队列被广泛应用在各类编程中。大数据时代对跨进程、跨机器的通讯提出了更高的要求,和以往相比,分布式队列编程的运用几乎已无处不在。但是,这种常见的基础性的事物往往容易被忽视,使用者往往会忽视两点:使用分布式队列的时候,没有意识到它是队列。有具体需......
  • openmmlab-实战营二期-openmmlab概述课(一)
    openmmlab实战营-二期-openmmlab概述课(一)目录openmmlab实战营-二期-openmmlab概述课(一)openmmlab概述openmmlab各算法库详细介绍目标检测算法库MMDetection目标检测算法库MMYolo文字检测识别算法库MMOcr3D目标检测算法库MMDetection3D旋转目标检测算法库MMRotate图像分割算法......
  • Hive高级函数实战
    函数的基本操作和mysql一样的,hive也是一个主要做统计的工具,所以为了满足各种各样的统计需要,它也内置了相当多的函数showfunctions;#查看所有内置函数descfunctionfunctionName;#查看指定函数的描述信息descfunctionextendedfunctionName;#显示函数的扩展内容Hiv......
  • 进程注入分析实战——通过process explorer可以看到lab12-01.dll在运行时加载了, 要查
     要查看dll被哪个进程所使用,可以在processexplorer里搜索!  这个技巧在分析恶意DLL加载时候非常有用!!!笔记可以通过processexplorer查看进程注入的dll,比如注入后可以看到lab12-01.dll在注入的运行进程里。    启动器Launcher用来加载恶意代码使用,通常在资源中包含一个exe或d......
  • 实战:实现缓存和数据库一致性方案
    哈喽大家好,我是阿Q!最近不是正好在研究canal嘛,刚巧前两天看了一篇关于解决缓存与数据库一致性问题的文章,里边提到了一种解决方案是结合canal来操作的,所以阿Q就想趁热打铁,手动来实现一下。架构文中提到的思想是:采用先更新数据库,后删除缓存的方式来解决并发引发的一致性问题;采用异......
  • 原理+配置+实战,Canal一套带走
    哈喽大家好,我是阿Q!前几天在网上冲浪的时候发现了一个比较成熟的开源中间件——Canal。在了解了它的工作原理和使用场景后,顿时产生了浓厚的兴趣。今天,就让我们跟随阿Q的脚步,一起来揭开它神秘的面纱吧。简介canal翻译为管道,主要用途是基于MySQL数据库的增量日志Binlog解析,提供增......
  • 3、实战案例:部署基于JAVA的博客系统JPress
    官方网站:http://www.jpress.io/安装包下载第一步:[root@ubuntu2004]#mkdir/data/jpress/-p创建网站数据存放的目录,ROOT可以不建把下载好的包拉进/data/jpress/目录,并改名为ROOT.war,它会自动解压成一个ROOT文件夹[root@ubuntu2004jpress]#rz-Erzwaitingtoreceive.[root@......