Netty in Action (二十) 第十章节 codecs

第十章,第十一章序

对于网络而言,数据只是原始字节序列,但是我们的程序将这些字节按照某种方式去组织成我们能够看懂的语言,我们一般称这些信息叫“信息”,将信息转换成字节或者从网络中将字节装换成我们能够看懂的信息这些都是我们网络传输中最最常见的任务之一,你可能需要在标准的格式或者协议下工作,例如FTP协议或者Telnet协议,或者是从第三方自定义的专有协议,亦或者是根据字自已的应用去继承一种已有的信息格式

处理将网络中的数据转化成应用数据的组件叫做解码器或者编码器,相对应而言,一个单独的组件这两个功能都有的情况下,我们称这个组件叫做译码器,Netty提供了大量的工具来创造这些译码器,我们可以通过重构一些类来实现现在比较著名的一些通用的传输译码器例如HTTP,Base64等,通过这样的方式来满足你自定义的需求

第十章内容是对编码和解码进行了基本的介绍,你将会通过学些一些经典的用户案例了解到Netty最基本的译码器,随后你将会学会如何将这些类合适地运用到整个Netty框架里,你会发现这些译码器可以构建与我们以前学过的组件的API之上,所以你能够很快的上手运用这些类

在第十一章节中,我们将探索Netty提供的解码器和编码器在一些更加特殊具体的场景下如何工作,Websocket是比较令人感兴趣的一个小节,所以我们准备在第三个模块详细讨论这个先进的网络传输协议

第十章本章内容包括:

1)对编码器,解码器,译码器的一个基本讲解

2)Netty的编码工具类

由于很多专业的框架提供了很多标准的架构设计模式,一些比较通用的数据处理模式经常作为实战应用的最热候选方案,这些比较常用的解决方案可以为开发者节省很多时间和经历

这个章节的当仁不让的主题肯定是编码或者解码,或者是将数据从一个格式转化成另一种格式,处理这种问题的组件我们经常称之为译码器,Netty为很多协议提供了一些组件来简化自定义译码器的实现,举例来说,你会发现Netty的译码器可以无缝地支持POP3,IMAP,SMTP协议的实现

10.1 What is a codec?

每一个应用框架都需要定义如何在远程传输的两端去将原始字节转化或者解析原始字节,需要考虑如何将目标信息转成原始字节或者做相反的过程将原始字节转化成目标编程信息,这种转化的逻辑是由译码器做处理的,它包含了编码和解码的功能,每一个译码器都可以将一串字节从一个格式转化成另一个格式,那么我们怎么区分编码和解码的区别

想象一个场景,一个信息以一种有结构化的字节序列形式用于我们的应用中,那么编码器就是将这个信息转化成一种适合在网络中传输的格式,例如字节流,相对应的就是译码器,将从网络传输中返回的字节流转化成程序能够识别的信息格式,简而言之,编码器操作一个输出数据,译码器操作一个输入数据

请将这个知识背景牢记心中,然后我们一起看Netty是如何去实现这两种组件的

10.2 Decoders

在这个小节中,我们将会调查Netty的解码器,通过几个具体的案例来说明在什么时候合适的去使用这些类,这些类主要分成两种不同的用户案例:

1)将字节解码成信息-----ByteToMessageDecoder和ReplayingDecoder

2)将一种信息格式转化成另一种信息格式----------MessageToMessageDecoder

因为解码器负责将传输的输入数据转化成另一种格式,所以如果你知道Netty的解码器实现了ChannelInboundHandler的话,你一定不会惊讶

那么什么时候你可以使用一个解码器呢?很简单:当你需要将输入数据传输到ChannelPipeline中的下一个ChannelInboundHandler的时候,还有就是我们需要感谢ChannelPipeline的设计,你可以将多个解码器以链式的格式去将所有的格式连接起来去处理任意复杂的传输逻辑,这样做的主要目的就是Netty支持模块化和可重复利用性

10.2.1 Abstract class ByteToMessageDecoder

将字节转化成信息对于使用Netty提供的抽象类ByteToMessageDecoder来说就是一个很普通的任务,由于你无法知道远程数据输入端是否能够一次性传输整个信息,这个类将会缓冲所有的输入数据直到输入的字节满足被处理的要求,表10.1向你展示了这个类最为重要的两个方法

我们举例说明如何使用该类,假设你需要接收一个字节流,这个字节流中只包含简单的int变量,每一个int变量可以被单独处理,在这个案例中,你在输入的ByteBuf中每读到一个int变量就将其传输到管道中的下一个ChannelInboundHandler中去,为了对ByteBuf中的字节流进行解码,我们需要继承ByteToMessageDecoder

我们的设计如图10.1展示一样:

每次从输入的ByteBuf中读取四个字节,解码成一个int型变量,然后将解码后的int变量加入到List中,当没有更多的对象加入到List的时候,那么List中的内容将会发送到下一个ChannelInboundHandler中去

下面的代码清单展示了ToIntegerDecoder的代码

尽管ByteToMessageDecoder可以让这种转化模式变得很简单,但是你可能会发现每次你都需要去验证输入的ByteBuf中是否有足够的字节数去转化成int型的时候都要进行数量的验证,下一个小节我们将会讨论ReplayingDecoder,一个特殊的Decoder,这个解码器可以消除这个步骤以很小的代价

TIPS:在译码器中的技术引用:

我们在第五和第六章节中提及过,计数应用有一些需要值得主要的事情,在解码器和译码器中例子中,其实过程很简单,一旦信息被解码或者译码之后,它将会自动调用ReferenceCountUtil.release(message)方法释放自己,如果你需要继续获取这些对象的引用用来以后使用的话,你可以调用ReferenceCountUtil.retain(message)方法来获取这个对象的持续引用,因为对计数引用值的增加,这样可以防止该信息被释放

10.2.2 Abstract
class ReplayingDecoder

ReplayingDecoder继承了ByteToMessageDecoder使我们可以从调用readableBytes的方法中解放出来,它通过用自定义的ByteBuf实现ReplayingDecoderBuffer来包装输入的ByteBuf来完成这种需求,这个类完整的声明如下:

参数S指定了用来进行状态管理时的类型,在这里的Void指定了没有值将被指定,下面的代码清单用ReplayingDecoder重新实现了ToIntegerDecoder2的功能

之前,int型的变量是从ByteBuf中演化过来的然后添加到List中,如果不足的字节输出,那么readInt的方法就会抛出一个错误,这个错误会被基础的类捕获且处理,然后decode方法也会当更多的字节输出的时候再次被调用执行

以下关于ReplayingDecoder的几个重要的方面需要注意:

1)它并不支持所有的ByteBuf的所有操作方式,如果一个不支持的方式被调用了,那么一个UnsupportedOperationException异常将会被抛出

2)ReplayingDecoder的执行效率是稍稍比ByteToMessageDecoder略差

如果你通过将10.1和10.2的代码清单作比较的话,你会发现后者更加简单,当然例子本身就是很简单的,但是请你牢记于心,在真实的生产环境中使用ByteToMessageDecoder还是使用ReplayingDecoder的选择其实是很重要的,会影响你程序的性能,我们这里有个用户使用准则你可以参考一下:在很简单的业务场景下不想引入额外的复杂性的情况下使用ByteToMessageDecoder,否则就使用ReplayingDecoder

TIPS:更多的decoders

下面是一些类用来处理更加复杂的用户需求

1)io.netty.handler.codec.LineBasedFrameDecoder这个类被Netty内部使用,使用行结束控制符\n或者\r\n来解析信息数据

2)io.netty.handler.codec.http.HttpObjectDecoder这个解码器用来处理HTTP类型的数据

你可以在io.netty.handler.codec的子包下找到一些额外的译码器和解码器实现用来处理一些比较特殊的用户案例,详细地信息请查看Netty的Java文档

10.2.3 Abstract class MessageToMessageDecoder

在这个小节中,我们将会说明如何在两种信息格式之间做转换,举例来说将一种合适的POJO转化成另一种格式的POJO,我们使用基类的抽象类

参数I指定了decode方法中的msg的参数的类型,decode是唯一一个需要实现的方法,表10.2展示了这个方法的细节

在这个例子中,我们写了一个继承与MessageToMessageDecoder<Integer>的IntegerToStringDecoder的解码器,它的decode方法将会将Integer的参数转化成String型,下面是这个方法的完整声明:

与之前一样,解码器的输出的String类型的变量将会加入到List中,然后传输到管道中的下一个Handler中去

这个设计图如10.2图所示:

下面的代码清单是IntegerToStringDecoder的具体实现:

一些更加复杂的例子,你可以去查看io.netty.handler.codec.http.HttpObjectAggregator类,它继承了MessageToMessageDecoder<HttpObject>

10.2.4 Class
TooLongFrameException

由于Netty是一个异步框架,所以有时候你需要把缓存字节数放入到内存中直到你可以读取解码这些字节,所以你不能将解码的缓存字节大于可利用的内存空间,为了处理这种比较常见的担忧,Netty提供了TooLongFrameException的类型的异常,这个异常将会在解码的时候,如果一个帧数据超过了指定大小限制的时候抛出

为了阻止这种异常,你可以设置帧数据的最大字节的阀门,如果超过了,就会导致抛出TooLongFrameException的异常,关于这个异常的处理完全取决于解码端的用户,有些场景下,例如HTTP的协议的时候,可能会允许你返回一个特殊的返回,在一些其他的场景下,可能会只有一个选择就是关闭连接

代码清单10.4中向你展示了一个ByteToMessageDecoder如何充分使用TooLongFrameException的异常来通知管道中的其他ChannelHandler发生了帧数据超过阀门的异常,请注意这种保护是很重要的,尤其是你工作的协议是有可变大小的帧数据的情况下

截止到目前为止,我们学习了解码器的一些常用的使用方式,也学习了Netty提供的一些构建这些类的一些抽象类,但是解码只是译码硬币的一面,在解码器的另一面就是编码器了,编码器可以将信息转化成适合传输的类型,关于编码器的详细API将会在下一个章节讨论

10.3 Encoders

回顾一下我们之前的定义,一个编码器实现了ChannelOutboundhandler,将一种格式的数据转化成另一种格式用于传输,这与我们学习过的译码器是有相反的功能,Netty提供了很多类来帮助你写入如下功能的编码器

1)从message到字节的编码

2)从message到messag的编码

我们将通过学习一个抽象基础类MessageToByteEncoder作为我们学习这些编码器的开始

10.3.1 Abstract class MessageToByteEncoder

之前我们使用ByteToMessageDecoder来将字节转化成我们能够识别的message,现在我们使用MessageToByteEncoder来做相反的事情,表10.3展示了MessageToByteEncoder的API

可能你已经注意到这个类只有一个方法,但是对应的解码器却由两个方法,关于为什么解码器有两个方法的原因是因为解码器通常需要在Channel关闭的时候,生成最后一个信息,这明显不会再编码器中出现,在连接关闭之后是没有意义再去生产一个信息的

图10.3展示了一个ShortToByteEncoder接收一个Short类型的实例作为信息,将其编码成原始的short变量,然后写入到ByteBuf中,最后传输到管道中的写一个ChannelOutboundHandler中,每一个输出的Short将会占据ByteBuf中的2个字节

下面的代码清单展示了ShortToByteEncoder的具体实现:

Netty提供了几个专业的MessageToByteEncoder依赖于这些Encoder你可以构建你自己的实现,类WebSocket08FrameEncoder提供了一个很好的实战示例,你将会在包io.netty.handler.codec.http.websocketx下

10.3.2 Abstract class MessageToMessageEncoder

你已经了解了如何将一个输入数据从一个格式转化成另一种格式的解码过程了,为了完成译码器的整个蓝图,我们将向你展示对于一个输出数据来说怎么从一个格式转化成另一种格式,MessageToMessageEncoder的encode方法就提供了这种实现,如表10.4中描述的一样

为了说明这点,代码清单10.6写了一个IntegerToStringEncoder,该类继承于MessageToMessageEncoder,这个设计如图10.4展示:

在下面的代码清单中,一个编码器将Integer转化成String然后存储到List中

关于更多的MessageToMessageEncoder的专业使用,你可以查看io.netty.handler.codec.protobuf.ProtobufEncoder,这个类可以处理Google定义的Protocol Buffer定义的数据格式

10.4 Abstract codec classes

虽然我们将解码器和编码器作为两种不同的组件学习过了,但是有时候你会发现将编码和译码放在同一个类中来管理数据传输会更加的高效,Netty的抽象译码器类可以帮助我们完成这个功能,因为译码器成对地绑定了译码器和编码器用来处理我们刚刚学过的两种类型的操作,你可能会质疑,是不是这些译码器的类是不是继承了两个类,一个是ChannelInboundHandler一个是ChannelOutboundHandler

在真实的生产环境下,我们为什么总是不用这个符合的译码器而是优先选用编码器或者译码器呢,因为单独使用这两个组件可以尽可能地最大化代码的可重复利用性和扩展性,这是符合Netty的设计原则的

当我们在研究译码器的时候,我们会将译码器与相对应的译码器或者编码器做比较和权衡的

10.4.1 Abstract class ByteToMessageCodec

让我们从一个案例开始入手学习译码器,在这个案例中我们需要将字节解码成某种类型的信息,可能是一个POJO,然后对这个POJO再次编码,ByteToMessageCodec这个类可以帮助我们完成这个功能,因为它内部整合了ByteToMessageDecoder和与ByteToMessageDecoder功能相反的类MessageToByteEncoder,关于ByteToMessageCodec这个类的一些核心方法如表10.5展示

任何一种请求响应类的协议使用ByteToMessageCodec这个类应该是一个不错的候选方案,例如,在一个SMTP实现中,此译码器可以输入的字节中读取然后将其解码成一个自定义的数据类型,一般称之为“SmtpRequest”,在接收端,当一个响应到达的时候,一个对应的SmtpResponse也会被创建,这个会将刚才解码的对象重新编码成一个新的对象用于传输

10.4.2 Abstract class MessageToMessageCodec

在章节10.2.2中我们学习一个继承于MessageToMessageEncoder的类用于将一个message转化成另一种格式,我们可以使用一个单独的类MessageToMessageCodec来完成这一个轮询的过程,MessageToMessageCodec是一个指定参数的类,它的具体定义如下:

这个方法一些比较重要的方法如表10.6展示:

decode方法将一个INBOUND_IN类型的信息转化成OUTBOUND_IN类型,然后encode方法做这相反的事情,这可以帮助你想象INBOUND_IN类型的数据可以直接传输,然后OUT_BOUND信息可以被应用直接处理应用

尽管这个译码器看起来的确有点深奥,但是使用它的时候还是相对简单的:当你使用这两个不同的message的API的时候来回将信息转换四次,我们会经常遇到这种场景,例如当我们不得不与一个类型的数据API交互的时候使用这个类还是很合适的

TIPS:webSocket protocol

接下来的例子我们是参考WebSocket的写的MessageToMessageCodec,它是一个比较现代的协议,它可以做到完全的浏览器端和服务器端的双向通信,我们会在第11章节深度地讨论Netty对Websocket的支持

代码清单10.7展示了在一次通信中发生了多少次的转化,我们自定义的WebSocketConvertHandler继承了MessageToMessageCodec用WebSocketFrame指定了INBOUND_IN参数的类型,用MyWebsocketFrame类指定了OUTBOUND_IN变量,后者是WebSocketConvertHandler类本身的静态内部类

10.4.3 Class CombinedChannelDuplexHandler

我们之前提及过,如果联合一个编码器或者一个解码器会对我们的可重复利用性造成一定的影响,但是我们有办法可以消除这种问题并且我们不需要将一个编码器和一个译码器放在同一个单元类里面,这个解决方法是由CombinedChannelDuplexHandler提供的,它的完整声明如下:

这个类你可以将其看成一种ChannelInboundHandler和ChannelOutboundHandler的容器,通过可以继承一对编码器和译码器,通过这种方式,我们就不需要直接继承一个抽象译码器来实现我们自己的功能了,我们用下面的一个完整的例子来说明这个问题:

首先,我们可以看下下面代码清单中的ByteToCharDecoder,注意到这种实现继承与ByteToMessageDecoder,因为这个类需要从ByteBuf中读取char类型的数据

在这里的decode方法每次从ByteBuf中提取2个字节然后将其当做一个char写入到List中,注意这里可以自动装箱成一个Character的对象

下面的代码清单中是CharToByteEncoder类,这个再将char重新转化成字节,这个类继承与MessageToByteEncoder,因为它需要将char重新编码成字节,这个可以直接由ByteBuf的方法直接写入:

现在我们已经有了编码器和解码器了,让我们将其联合起来构建成一个译码器,下面的代码清单展示了如何实现这个功能

你可以看到,这种方式在某些使用场景下相比于直接使用译码器而言可能更加简单更加灵活,当然这归根结底而言是与个人喜好有关系的

10.5 Summary

在这个章节中,我们学习了Netty译码器的API的使用,学习了如何编写编码器和译码器,你也了解到为什么使用这些API比直接使用ChannelHandler的API好

你了解到一个抽象的译码器是如何在一个类中提供编码和译码的功能的,当然,如果你需要更多的灵活性或者可重复利用性的话,你也可以将两种功能联合起来,且是在不需要继承任何抽象译码器的基础上

在下一个章节中,我们将一起讨论ChannelHandler的实现,讲解作为Netty框架本身组成的一部分的原始译码器的更多实现细节,并且使用原始译码器来完成一些特殊的协议或者任务。

时间: 2024-10-19 18:44:25

Netty in Action (二十) 第十章节 codecs的相关文章

Netty in Action (十五) 第六章节 第一部分 ChannelHandler和ChannelPipeline

本章内容包括: 1)ChannelHandler和ChannelPipeline的APIs 2)检测内存泄漏 3)异常处理 在之前的一个章节中,我们学习了ByteBuf,Netty的数据容器,在这个章节中,我们将讲解Netty的数据流和对应的处理组件,然后我们将我们已经学过的所有组件整合在一起 你已经知道多个ChannelHandler可以被链式的放入ChannelPipeline来将所有的处理逻辑组织在一起,我们将学习包涵这些有关类的很多用户案例和他们之间的对应关系------ChannelH

Mina、Netty、Twisted一起学(十):线程模型

要想开发一个高性能的TCPserver,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,假设再搭配上高效率的代码.才干实现一个高大上的server. 可是假设不了解它们的线程模型.就非常难写出高性能的代码.框架本身效率再高.程序写的太差,那么server总体的性能也不会太高.就像一个电脑,CPU再好.内存小硬盘慢散热差,总体的性能也不会太高. 玩过Android开发的同学会知道,在Android应用中有一个非常重要线程:UI线程(即主线程). UI

(二) 第十二回:廊前无心曲动人 场边有意文偏心【林大帅作品】

(二) 第十二回:廊前无心曲动人 场边有意文偏心 回到教室,见那气氛杀,陈女先生立于案前,面带怒容.刘鹏亦是站着,两眼望那天花板上去了,林二只得硬着头皮进去.忽听见背后先生声音:“本堂课自习”便往门外去了,林二便偷偷问同桌阿诸,原来刘鹏不过小事,与那先生顶嘴.林二便与阿四论那欧罗巴蹴球,便提那日美云之事.阿四倒不在意,就嘟了一句:“还管我”就与林二商量与那哲理书院竞赛之事. 其时文会院队因队长国仁故,与那忠门队常有交接,且互有胜负.反那哲学,育青二旅,却视为死敌,每每有群殴之忧.阿四道:“阿仁通

中文数字转换成阿拉伯数字(一千二百三十四万五千六百七十八--&gt;12345678)

昨天老大问我又没有写过中文数字转换成阿拉伯数字,我说没有,我说那应该简单啊,就是将中文对应的数字换成阿拉伯数字就好了啊,比如一就换成1,二就换成2…十换成10.可是那么问题来了…… 一十二呢…不能是1102吧…这不就坑爹了吗?一百万呢………所有我苦苦思索,花费了我差不多半天的时间,终于写出了下面的程序. 1 public static void main(String[] args){ 2 3 Map<Character, String> numberMap = new HashMap<

(二)第十回 同日生辰情解契语 异姓兄弟冰释嫌隙[林大帅作品集]

(二)第十回 同日生辰情解契语 异姓兄弟冰释嫌隙               自那日众人商量了蹴球之事,次日,便邀了各班或段队.平日里皆识于球场,不过碰面一提,那学弟们便领命,携贴而去.每日功课已毕众人便在球场,这高二文科队,因前有阿四冲锋,后又林二坐镇,并有小谢辅佐,加之队长国仁.可谓遇佛杀佛,日后也是这等阵容,挑战兴化府各大书院.今晚林二踱至阿清店门口,不禁愣住,只见阿四与那三大个同桌吃面.就那阿清也忙着上菜,照顾不停.阿四见那廊下立着林二,便照顾过来.那三人也点头示意,阿四打个长嗝,叫阿清

《Netty in action》目录修复版本分享

最近阅读了Netty in action一书.深感外国友人的书籍编写能力强大.作者由简入深.精简描述了Netty的相关知识,如何使用等等. 本来想翻译一下的.尝试着翻译了一点之后.发现非常痛苦啊.ps.笔者英语不是很好. 很多时候需要自己先把英文理解成中文然后再拼凑起来.写出来的中文译文感觉好奇怪.所以就不拿出来献丑了.把网上的PDF电子书分享一下. 前段时间去找的电子书目录都存在问题的. NIA一书分为四个部分.16个章节.网上的第五版书籍是完整的.但是目录结构完全错误了.再次使用软件修复了.

Netty In Action中国版 - 第二章:第一Netty程序

本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本章将简介Netty的核心概念,这个狠心概念就是学习Netty是怎样拦截和处理异常.对于刚開始学习netty的读者.利用netty的异常拦截机制来调试程序问题非常有帮助.本章还会介绍其它一些核心概念.如server和client的启动以及分离通道的处理程序.本章学习一些基础以便后面章节的深入学习. 本

《Netty In Action中文版》第二章:第一个Netty程序

注:本篇内容出自<Netty In Action>一书:         注:本人原创译文,转载请注明出处! 本章介绍 获取Netty4最新版本 设置运行环境来构建和运行netty程序 创建一个基于Netty的服务器和客户端 拦截和处理异常 编写和运行Netty服务器和客户端 本章将简单介绍Netty的核心概念,这个狠心概念就是学习Netty是如何拦截和处理异常,对于刚开始学习netty的读者,利用netty的异常拦截机制来调试程序问题很有帮助.本章还会介绍其他一些核心概念,如服务器和客户端的

Netty In Action中文版 - 第四章:Transports(传输)

本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) Use-case(用例) APIs(接口) 网络应用程序一个非常重要的工作是数据传输. 数据传输的过程不一样取决是使用哪种交通工具,可是传输的方式是一样的:都是以字节码传输.Java开发网络程序数据传输的过程和方式是被抽象了的.我们不须要关注底层接口.仅仅须要使用Java API或其它网络框架如Net

Struts2(四)Action二配置

一.method参数 action package com.pb.web.action; public class HourseAction { public String add(){ System.out.println("执行添加操作!"); return "success"; } public String update(){ System.out.println("执行更新操作!"); return "success"