一、概念

网络传输的单位是字节,如何将应用程序的数据转换为字节,以及将字节转换为应用程序的数据,就要说到到我们该篇介绍的编码器和解码器。

将应用程序的数据转换为网络格式,以及将网络格式转换为应用程序的数据的组件分别叫作编码器和解码器,同时具有这两种功能的单一组件叫作编解码器。 Netty 提供了一系列用来创建所有这些编码器、解码器以及编解码器的工具,还可以按需定制通用的消息转换编解码器。

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

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

二、编码器

编码器将应用程序的数据转换为网络格式,出站消息时被调用,主要有两类:

1、将消息编码为字节:MessageToByteEncoder

public abstract class MessageToByteEncoder<I> extends ChannelHandlerAdapter{}
public class ShortToByteEncoder extends MessageToByteEncoder<Short> {

    /**
* 1、类型为 I 的出站消息被编码为 ByteBuf
* 2、该 ByteBuf 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。
*/
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Short aShort, ByteBuf byteBuf) throws Exception {
byteBuf.writeShort(aShort);
}
}

2、将消息编码为消息:MessageToMessageEncoder

public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter{}
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {

    /**
* 1、类型为 I 的出站消息被编码为目标类型 存入List 中
* 2、该 List 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。
*/
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}

三、解码器

解码器将网络格式转换为应用程序的数据,入站消息时被调用。

解码器比编码器多了一个 decodeLast 方法,原因是解码器通常需要在 Channel 关闭之后产生最后一个消息。这显然不适用于编码器的场景 —— 在连接被关闭之后仍然产生一个消息是毫无意义的。所以,当 Channel 的状态变为非活动时,这个方法将会被调用一次。可以重写该方法以提供特殊的处理。

解码器主要有两类:

1、将字节解码为消息:ByteToMessageDecoder 和 ReplayingDecoder

public abstract class ByteToMessageDecoder extends ChannelHandlerAdapter {}
public class MyDecoder extends ByteToMessageDecoder {

    private static final int MAX_FRAME_SIZE = 1024;

    /**
* 1、该方法被调用时,将会传入一个包含了传入数据的 ByteBuf,以及一个用来添加解码消息的 List.
* 2、对该方法的调用将会重复进行,直到确定没有新的元素被添加到该 List,或者Butebuf 没有更多可读取的字节为止。
* 3、List 的内容将会被传递给 ChannelPipeline 中的下一个 ChannelInboundHandler。
*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int readableBytes = byteBuf.readableBytes();
//不能让解码器缓冲大量的数据以致于耗尽可用的内存
if (readableBytes > MAX_FRAME_SIZE){
//跳过所有的可读字节
byteBuf.skipBytes(readableBytes);
throw new TooLongFrameException("数据超过可缓存字节...");
}
//假设需要解析 int 类型的消息(int 4个字节)
if (readableBytes > 4){
list.add(byteBuf.readInt());
} }
}

----分割线----

//类型参数 S 指定了用于状态管理的类型,其中 Void 代表不需要状态管理。
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder{}
public class MyReplayingDecoder extends ReplayingDecoder<Void> {

    /**
* 1、ReplayingDecoder 扩展了 ByteToMessageDecoder,并且自定义了 ByteBuf 的实现 ReplayingDecoderByteBuf。
* 2、ReplayingDecoderByteBuf 对要转换的消息的字节数进行内部管理,如果没有足够的字节使用,将会抛出一个 Signal,由ReplayingDecoder进行处理。
*
* @param byteBuf 传入的 ByteBuf 实际上是 ReplayingDecoderByteBuf*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(byteBuf.readInt());
}
}

继承 ByteToMessageDecoder 和继承 ReplayingDecoder 有什么区别呢?ReplayingDecoder 唯一的好处就是解码的时候不用进行字节数的判断,如上 ,因为它交由自定义的 ReplayingDecoderByteBuf 去处理了。但是 ReplayingDecoder 的效率稍慢于ByteToMessageDecoder。一般我们在这两个解码器中进行抉择的准则是:如果使用  ByteToMessageDecoder 不会引入太多的复杂性,那么请使用它;否则,请使用 ReplayingDecoder!

2、将消息解码为消息:MessageToMessageDecoder

public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter{}
public class IntegerToStringDecoder extends MessageToMessageEncoder<Integer> {

    /**
* 1、对于每个需要被解码为另一种格式的入站消息来说,该方法将会被调用。
* 2、解码消息随后会被传递给 ChannelPipeline 中的下一个 ChannelInboundHandler
*/
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Integer integer, List<Object> list) throws Exception {
list.add(String.valueOf(integer));
}
}

四、编解码器

Netty 的抽象编解码器类捆绑一个解码器/编码器对,主要用于在同一个类中管理入站和出站数据和消息的转换。

个人觉得这个编解码器略显鸡肋呀,还是喜欢将编码器和解码器分开来写。因为 Netty 设计的一个基本准则就是:尽可能地将两种功能(编码器、解码器)分开,最大化代码的可重用性和可扩展性。

编解码器也主要有两类:

1、字节消息编解码器:ByteToMessageCodec

public abstract class ByteToMessageCodec<I> extends ChannelHandlerAdapter {}
public class MyBytetoMessageCodec extends ByteToMessageCodec<Short> {

    @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
while (in.readableBytes() > 4){
out.add(in.readInt());
}
} @Override
protected void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception {
out.writeShort(msg);
}
}

2、消息转换编解码器:MessageToMessageCodec

public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN> extends ChannelHandlerAdapter {}
public class WebSocketConvertHandler extends MessageToMessageCodec<WebSocketFrame, WebSocketConvertHandler.MyWebSocketFrame> {

    /**
* 1、对于每个 OUTBOUND_IN 类型的消息,这个方法都会被调用。
* 2、这个消息将会被编码为 INBOUND_IN 类型的消息。
* 3、然后被转发给 ChannelPipeline 中的下一个 ChannelOutboundHandler
*/
@Override
protected void encode(ChannelHandlerContext ctx, MyWebSocketFrame msg, List<Object> out) throws Exception {
ByteBuf byteBuf = msg.getByteBuf().duplicate().retain();
switch (msg.getFrameType()) {
case BINARY:
out.add(new BinaryWebSocketFrame(byteBuf));
break;
case TEXT:
out.add(new TextWebSocketFrame(byteBuf));
break;
case CLOSE:
out.add(new CloseWebSocketFrame(true, 0, byteBuf));
break;
case CONTINUATION:
out.add(new ContinuationWebSocketFrame(byteBuf));
break;
case PONG:
out.add(new PongWebSocketFrame(byteBuf));
break;
case PING:
out.add(new PingWebSocketFrame(byteBuf));
default:
break;
} } /**
* 1、传入 INBOUND_IN 类型的消息,该方法会被调用。
* 2、这个消息会被解码为 OUTBOUND_IN 类型的消息。
* 3、然后被转发给 ChannelPipeline 中的下一个 ChannelInboundHandler
*/
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
ByteBuf byteBuf = msg.content().duplicate().retain();
if (msg instanceof BinaryWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.BINARY, byteBuf));
} else if (msg instanceof CloseWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.CLOSE, byteBuf));
} else if (msg instanceof TextWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.TEXT, byteBuf));
} else if (msg instanceof PingWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.PING, byteBuf));
} else if (msg instanceof PongWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.PONG, byteBuf));
} else if (msg instanceof ContinuationWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.CONTINUATION, byteBuf));
} else {
throw new IllegalStateException("Unsupported websocket msg " + msg);
} } public static final class MyWebSocketFrame { public enum FrameType {
BINARY,
CLOSE,
PING,
PONG,
TEXT,
CONTINUATION
} private final FrameType frameType;
private final ByteBuf byteBuf; public MyWebSocketFrame(FrameType frameType, ByteBuf byteBuf) {
this.frameType = frameType;
this.byteBuf = byteBuf;
} public FrameType getFrameType() {
return frameType;
} public ByteBuf getByteBuf() {
return byteBuf;
}
}
}

这个类写的还是很棒的~~

参考资料:《Netty IN ACTION》

演示源代码:https://github.com/JMCuixy/NettyDemo

Netty 系列六(编解码器).的更多相关文章

  1. Netty4.x中文教程系列(六) 从头开始Bootstrap

    Netty4.x中文教程系列(六) 从头开始Bootstrap 其实自从中文教程系列(五)一直不知道自己到底想些什么.加上忙着工作上出现了一些问题.本来想就这么放弃维护了.没想到有朋友和我说百度搜索推 ...

  2. Netty4.x中文教程系列(五)编解码器Codec

    Netty4.x中文教程系列(五)编解码器Codec 上一篇文章详细解释了ChannelHandler的相关构架设计,版本和设计逻辑变更等等. 这篇文章主要在于讲述Handler里面的Codec,也就 ...

  3. 6. 彤哥说netty系列之Java NIO核心组件之Buffer

    --日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第六篇. 简介 上一章我们一起学习了Java NIO的核心组件Channel,它可以看作是实体与实体之间的连接,而且需要与Buffer交 ...

  4. Netty系列之源码解析(一)

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel ...

  5. CSS 魔法系列:纯 CSS 绘制各种图形《系列六》

    我们的网页因为 CSS 而呈现千变万化的风格.这一看似简单的样式语言在使用中非常灵活,只要你发挥创意就能实现很多比人想象不到的效果.特别是随着 CSS3 的广泛使用,更多新奇的 CSS 作品涌现出来. ...

  6. WCF编程系列(六)以编程方式配置终结点

    WCF编程系列(六)以编程方式配置终结点   示例一中我们的宿主程序非常简单:只是简单的实例化了一个ServiceHost对象,然后调用open方法来启动服务.而关于终结点的配置我们都是通过配置文件来 ...

  7. SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性

    原文:SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 ...

  8. R语言数据分析系列六

    R语言数据分析系列六 -- by comaple.zhang 上一节讲了R语言作图,本节来讲讲当你拿到一个数据集的时候怎样下手分析,数据分析的第一步.探索性数据分析. 统计量,即统计学里面关注的数据集 ...

  9. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

随机推荐

  1. unity shader 常用函数列表

    此篇博客转自csdn的一位大牛. 中间排版出了一些问题 Intrinsic Functions (DirectX HLSL) The following table lists the intrins ...

  2. 在使用可变数组过程中遇到*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'问题

    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFD ...

  3. 谈谈npm依赖管理

    引言 现在的前端开发几乎都离不开nodejs的包管理器npm,比如前端在搭建本地开发服务以及打包编译前端代码等都会用到.在前端开发过程中,经常用到npm install来安装所需的依赖,至于其中的技术 ...

  4. ES6之命令妙用

     很多人都听说过ES6(也就是ECMAScript的新一代标准)并且对她充满了向往,下面通过一个ES6中小知识点——let命令,来解开她的神秘面纱,让大家初步认识一下ES6的语法规范.        ...

  5. rgba()和opacity之间的区别(面试题)

    rgba()和opacity之间的区别: 相同点:rgba()和opacity都能实现透明效果: 不同点:opacity作用于元素,以及元素中所有的内容: rgba()只用于于元素的颜色,及背景色: ...

  6. NuGet 构建服务器与常用命令

    公司出于某些原因需要自己在内部网络搭建一个私有的 Nuget 服务器,而且要运行在 Linux服务器上面.如果说 Windows 下搭建的话很简单,直接在项目当中引入 Nuget 的库就 OK,这儿的 ...

  7. sql server 索引阐述系列六 碎片查看与解决方案

    一 . dm_db_index_physical_stats 重要字段说明 1.1 内部碎片:是avg_page_space_used_in_percent字段.是指页的填充度,为了使磁盘使用状况达到 ...

  8. 健康,home? [java的内存浅析]

    摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 乐观上上,how can other kno u,u r yoursel ...

  9. iOS逆向开发(3):锁定APP的目标类与函数 | reveal | lldb | debugserver | 远程调试

    之前介绍了怎么获取APP的所有类的结构信息,这个有什么用呢?用处大了,比如以这一步为基础,下一步通过注入来做更多研究工作. 注入的最小单位是函数,实际上,编译执行的程序在编译后,类就不复存在了,留下来 ...

  10. Consul安装使用

    安装 安装环境: Mac 我参照官网下载,然后解压,然后添加到PATH,并不能用.最终使用brew安装成功. brew install consul 安装成功后,文件位于/usr/local/Cell ...