Codec

来源:互联网 发布:如何开网络棋牌室赚钱 编辑:程序博客网 时间:2024/05/16 08:57

已经提供的ChannelHandler和Codec

本章包括

  • 使用SSL/TLS使得Netty安全

  • 构建Netty HTTP/HTTPS 应用

  • 处理空闲连接和超时

  • 基于分隔符和长度协议的解码器

  • 写大的数据

  • 序列化数据

前一章展示了如何创建你的codec。有了这些知识你就可以为你的应用写 codec了。 然而,如果Netty提供了一些标准的ChannelHandlers and codecs岂不是更好。Netty包含许多许多的协议实现,例如http实现,所以这些都省的你重新完成。

这些实现可以被重新使用,使得你可以关注具体的问题。本章将向你如何使用SSL/TLS使你的应用更加安全,如何书写可扩展 HTTP 服务器,自己如何使用相关的协议,例如websocket和Google的SPDY来使的你的应用性能更加好。所有的这些都是普通的,有时候是必须的应用。本章将会介绍压缩,当大小同样有作用的时候。

使用SSL/TLS使得Netty应用更加安全

通过网络传输数据默认情况下并不安全,所有的传输数据可以通过简单的文本或者二进制协议传输,很容易进行解码。这样如果你想秘密的传输数据将会存在问题。加密在这种场景下起作用了。

TLS 和 SSL是十分出名的标准,它位于许多协议之上,使得数据保持私有。 例如,如果你使用HTTPS 或者SMTPS,你会使用加密。即使你没有意识到这些,你已经在某种程度上接触过加密的基础。

对于SSL/TLS, ,Java 包含一个抽象叫做SslContext 和SslEngine。实际上,

SslContext可以被用来过去一个新的 SslEngine,它可以被用来进行加密和解密。它高度可配置来支持具体的加密解密,这超出了本章的范围。

Netty 继承Java的 SSL 引擎来添加更多的功能使得它更适于基于Netty的应用。 它包含了一个ChannelHandler 叫做SslHandler ,它包裹了一个SslEngine进行网络传输上的加密和解密。

图 8.1 展示SslHandler实现的数据流。

SslHandler

图 8.1 SslHandler

#1 流入的加密数据通过SslHandler 解析进行解密。#2 前面的加密数据被 SslHandler解密。#3 简单的未加密数据通过SslHandler。#4 SslHandler加密数据,并将数据传出。

列表 8.1 向你展示如何使用SslHandler ,

使用 ChannelInitializer将 它添加到ChannelPipeline中。通常在Channel创建之后你是这样创建ChannelPipeline的。

Listing 8.1 添加SSL/TLS 支持

public class SslChannelInitializer extends ChannelInitializer<Channel>{private final SSLContext context;private final boolean client;private final boolean startTls;public SslChannelInitializer(SSLContext context, boolean client,boolean startTls) { #1this.context = context;this.client = client;this.startTls = startTls;}@Overrideprotected void initChannel(Channel ch) throws Exception {SSLEngine engine = context.createSSLEngine(); #2engine.setUseClientMode(client); #3ch.pipeline().addFirst("ssl",new SslHandler(engine, startTls)); #4}}#1 使用构造函数传递SSLContext ,是否是客户端,和startTls 。#2 使用SslContext过去一个新的SslEngine。每一个SslHandler 实例使用一个新的SslEngine。#3 设置 SslEngine是客户端还是服务器端模式#4 将SslHandler 添加到pipeline 中作为第一个处理器

这里有一点要注意,几乎在所有的场景下 SslHandler必须是 ChannelPipeline里的第一个处理器。也许会有一些例外,但是还是要以它为标准。回忆ChannelHandler章节,ChannelPipeline对于流入数据是后进先出,对于流出的数据是先进先出。在第一个位置添加SSL 处理器确保其他处理器在加密之前对数据进行自己的转换逻辑,从而确保所有的处理器处理在Netty服务器上是安全的。

SslHandler 有一些有用的方法,如图表8.1,所示,你可以使用这些方法来改变一些行为,或者在SSL/TLS 握手完成的时候获得通知 (在两者握手的过程中,他们验证彼此,并且选择两者都支持的加密方式)。 SSL/TLS 握手是自动完成的。

表格 8.1 更改ChannelPipeline的方法

|名称| 描述|

| ————- | —–:|

|setHandshakeTimeout(…)||

|setHandshakeTimeoutMillis(…)||

|getHandshakeTimeoutMillis()|获取和设置超时时间,在超时时间之后握手会失败,握手的ChannelFuture 会被通知。|

|setCloseNotifyTimeout(…)||

|setCloseNotifyTimeoutMillis(…)||

|getCloseNotifyTimeoutMillis()|获取和设置超时时间,在这时间之后,关闭通知会超时,连接会关闭。这会导致关闭通知的ChannelFuture 失败。|

|handshakeFuture(…)|返回一个ChannelFuture ,它会在握手完成后被通知。如果握手在返回 ChannelFuture 之前完成,那么它包含上一次的握手结果。 |

|close(…)|发送 close_notify 来请求关闭和销毁。|

构建 Netty HTTP/HTTPS应用

HTTP/HTTPS是目前使用最多的协议之一,随着智能手机的成功,它受到了更多的关注。在今天,几乎每一个公司都有一个主页, 你可以通过HTTP 或者HTTPS访问 ,但它不是只能用于这里。许多的服务通过HTTP/HTTPS 暴露服务,这样就可以做到跨平台。

幸运的是,Netty存在一些处理器允许你使用HTTP,你不在需要写codec。

Netty的HTTP decoder, encoder, 和codec

HTTP 使用了请求-响应模式,意味着客户端发送HTTP请求到服务器端,服务器端返回HTTP响应。Netty通过提供不各种各样的encoder和decoder来处理HTTP协议,使得操作HTTP很容易。图 8.2 和

8.3分别 展示了产生的指令和处理特定的HTTP请求和响应。

http请求部分

图 8.2 HTTP 请求部分

#1 HTTP-Request第一部分包含头部信息 #2 HttpContent 部分包含一块一块的数据#3 特殊的HttpContent子类型,标志HTTP请求的结尾,可能包含尾部头信息。 #4 一个完整的HTTP请求,包含所有的东西。

http响应部分

图 8.3 HTTP 响应部分

#1 HTTP响应第一部分包含头部信息 #2 HttpContent 部分包含一块一块的数据#3 特殊的 HttpContent子类型标志HTTP响应的结尾,可能包含尾部头信息。 #4 完整的HTTP响应包含所有的东西 

如图 8.2 和 8.3分别所示,HTTP请求和响应可能包含多于一个消息。它的结尾通常包含LastHttpContent消息。

FullHttpRequest 和FullHttpResponse 是特殊的子类型,代表完整的请求和响应。所有的HTTP消息(FullHttpRequest,

LastHttpContent和列表 8.2中展示的) 实现 HttpObject 接口。

表格 8.2给出了 HTTP decoders 和encoders,它们负责处理和产生消息。

表格 8.2 HTTP decoder 和 encoder

|名称| 描述|

| ————- | —–:|

|HttpRequestEncoder| 将HttpRequest 和HttpContent 消息编码为字节数组|

|HttpResponseEncoder| 将HttpResponse 和 HttpContent 消息编码为字节数组|

|HttpRequestDecoder| 将字节解码为HttpRequest 和HttpContent消息|

|HttpResponseDecoder| 将字节数组解码为HttpResponse 和 HttpContent 消息|

列表 8.2 添加HTTP支持

public class HttpDecoderEncoderInitializerextends ChannelInitializer<Channel> {private final boolean client;public HttpDecoderEncoderInitializer(boolean client) {this.client = client;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();if (client) {pipeline.addLast("decoder", new HttpResponseDecoder()); #1pipeline.addLast("encoder", new HttpRequestEncoder()); #2} else {pipeline.addLast("decoder", new HttpRequestDecoder()); #3pipeline.addLast("encoder", new HttpResponseEncoder()); #4}}}#1 给客户端添加HttpResponseDecoder解码器,客户端会从服务器端接收响应。 #2 给客户端添加 HttpRequestEncoder 编码器,客户端会向服务器端发送请求。 #3 服务器端添加HttpRequestDecoder 解码器,它会接收来自客户端的请求。#4 添加服务器端HttpResponseEncoder 编码器,服务器会向客户端发送响应。

除了向ChannelPipeline添加一个编码器和解码器,还有简化做法,你可以只添加一个codec。你可以只添加HttpClientCodec 和HttpServerCodec。

在向ChannelPipeline中添加了encoder,decoder和codec之后,你可以在不同的HttpObject消息上操作。由于HTTP请求和响应可能由不同的消息组成,你可能需要处理不同部分,还有可能需要聚合它们。这些很繁琐。为了解决这些问题,Netty提供了聚合器,它可以将消息部分聚成FullHttpRequest 和FullHttpResponse 消息,所以你不用担心接收片段。下一部分将详细介绍聚合。

HTTP 消息聚合

上一部分已经介绍,当处理HTTP的时候,你可能接收到HTTP片段,如果不这样Netty会在接收之前缓存整个消息。

有些场景你想处理整个HTTP消息,即使一些内存开销也可以。为了达到这个目的,Netty提供了 HttpObjectAggregator。

有了HttpObjectAggregator ,Netty 将聚合HTTP消息部分,只将整体的FullHttpResponse 和 FullHttpRequest 路由到ChannelPipeline中的下一个ChannelHandler 。这样就消除了片段的担心,确保你只对整体消息操作。

自动聚合向ChannelPipeline中添加另一个ChannelHandler这么简单。列表 8.3展示了如何使聚合可用。

列表 8.3 自动聚合 HTTP消息片段

public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {private final boolean client;public HttpAggregatorInitializer(boolean client) {this.client = client;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();if (client) {pipeline.addLast("codec", new HttpClientCodec()); #1} else {pipeline.addLast("codec", new HttpServerCodec()); #2}pipeline.addLast("aggegator",new HttpObjectAggregator(512 * 1024)); #3}}#1 在客户端状态下添加HttpClientCodec#2 在服务器端状态下添加HttpServerCodec #3 向ChannelPipeline中添加HttpObjectAggregator,使用最大消息大小512kb。在消息大于这个之后会抛出一个 TooLongFrameException 异常。

你都看到了使用Netty自动聚合消息片段很容易。为了使得你的服务免于遭受DoS 攻击,你需要选择一个合适的最大消息大小值。这个大小究竟是多少,依赖于你的使用场景,并发请求,响应还有可用的内存资源。

HTTP 压缩

当使用HTTP的时候,考虑压缩需要网络传输的数据很理智。压缩会带来性能代价,它会增加CPU的压力。由于今天的计算能力不像以往那样存在问题,再大多数时候使用压缩是不错的主意。

Netty 提供了 “gzip” 和“deflate”支持, 分别提供了

ChannelHandler 实现: 一个用于压缩,一个用于解压缩。

压缩和解压缩如列表 8.4所示。

列表 8.4 自动压缩HTTP 消息

public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {private final boolean client;public HttpAggregatorInitializer(boolean client) {this.client = client;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();if (client) {pipeline.addLast("codec", new HttpClientCodec()); #1pipeline.addLast("decompressor",new HttpContentDecompressor());#2} else {pipeline.addLast("codec", new HttpServerCodec()); #3pipeline.addLast("decompressor",new HttpContentDecompressor());#4}}}#1 客户端状态下添加HttpClientCodec #2 客户端状态下添加HttpContentDecompressor ,服务器可能向我们发送压缩的内容#3 服务器端状态下添加HttpServerCodec#4 添加 HttpContentCompressor,客户端支持压缩,我们想压缩他 

使用 HTTPS

本章的前面已经提及如何通过加密保护内容在网络中传输。你同样可以通过HTTPS,达到此目的,Netty中提供了一些稳定的

ChannelHandler,你可以通过这些处理器很容易的达到目的。

你需要做的只是将SslHandler混入其中, 如列表8.5所示。

列表 8.5 使用 HTTPS

public class HttpsCodecInitializer extends ChannelInitializer<Channel> {private final SSLContext context;private final boolean client;public HttpsCodecInitializer(SSLContext context, boolean client) {this.context = context;this.client = client;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();SSLEngine engine = context.createSSLEngine();engine.setUseClientMode(client);pipeline.addFirst("ssl", new SslHandler(engine)); #1if (client) {pipeline.addLast("codec", new HttpClientCodec()); #2} else {pipeline.addLast("codec", new HttpServerCodec()); #3}}}#1 添加SslHandler 来使用HTTPS#2 客户端状态下添加HttpClientCodec #3 服务器端状态下添加HttpServerCodec

列表 8.5 Netty的Pipeline如何使得应用灵活这一部分例子展示了Handler使得网络编程足够强大的方式。 HTTP使用最广泛的互联网协议之一,Netty提供了所有必备的工具来帮助你快速简单的开发基于HTTP的应用。 下一部分我们将介绍HTTP协议的扩展之一 : WebSockets.

使用 WebSockets

HTTP很好,但是如果你想实时的发布消息该怎么办?以前,你可以通过后台的轮寻来完成,但是那种方法不是最优的,而且有可能很慢。

WebSockets允许双向交换数据 (从客户端到服务器端,从服务器端到客户端) 而不需要使用请求-响应模式。

在WebSockets发展的早期,它只可以发送文本,现在支持发送二进制数据,所以基于它你可以创建任何东西。

我们不会涉及 WebSockets 如何运作,它超出了本书的范围,但是WebSocket 传输的大体思想如图8.4.所示。

webSocket

图 8.4 WebSocket

#1 与服务器交互的客户端#2 与客户端交互的服务器#3 客户端通过HTTP(s) 来完成Websocket握手#4 传输升级到WebSockets

像你在图 8.4中看到的那样,连接起始于HTTP,升级到 WebSockets。

在你的应用中添加 WebSockets 很容易,因为Netty提供了ChannelHandler实现来支持。当使用Websocket的时候,会有不同的消息类型需要你处理。

表格8.3 展示了它们。

表格 8.3 WebSocketFrame 类型

|名称| 描述|

| ————- | —–:|

|BinaryWebSocketFrame|包含二进制数据的 WebSocketFrame |

|TextWebSocketFrame|包含文本数据的WebSocketFrame|

|ContinuationWebSocketFrame|包含文本或者二进制数据WebSocketFrame 属于前面的BinaryWebSocketFrame 或者TextWebSocketFrame|

|CloseWebSocketFrame| 代表关闭请求的WebSocketFrame ,包含关闭状态码和时间|

|PingWebSocketFrame| 要求发送一个PongWebSocketFrame的WebSocketFrame |

|PongWebSocketFrame| 被发送作为一个PingWebSocketFrame响应的WebSocketFrame |

为了保持简短,我们来看一看WebSocket 服务器使用了哪些。关于客户端的一些用法,可以参照Netty源代码中的例子。Netty提供了许多方式来使用WebSockets,但是使用最多的一个,是使用 WebSocketServerProtocolHandler来写一个 WebSockets 服务器,如列表 8.6所示。它为你处理This handles the握手 CloseWebSocketFrame,

PingWebSocketFrame和PongWebSocketFrame 。

列表 8.6 服务器上的 WebSocket支持

public class WebSocketServerInitializer extends ChannelInitializer<Channel>{@Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast(new HttpServerCodec(),new HttpObjectAggregator(65536), #1new WebSocketServerProtocolHandler("/websocket"), #2new TextFrameHandler(), #3new BinaryFrameHandler(), #4new ContinuationFrameHandler()); #5}public static final class TextFrameHandler extendsSimpleChannelInboundHandler<TextWebSocketFrame> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,TextWebSocketFrame msg) throws Exception {// Handle text frame}}public static final class BinaryFrameHandler extendsSimpleChannelInboundHandler<BinaryWebSocketFrame> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,BinaryWebSocketFrame msg) throws Exception {// Handle binary frame}}public static final class ContinuationFrameHandler extendsSimpleChannelInboundHandler<ContinuationWebSocketFrame> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,ContinuationWebSocketFrame msg) throws Exception {// Handle continuation frame}}}#1 添加HttpObjectAggregator 因为我们需要在握手过程中聚合HTTP请求。#2 添加 WebSocketServerProtocolHandler 来处理当请求发送至Websocket升级过程和升级之后的Ping, Pong 和 Close 帧。#3 TextFrameHandler 会处理TextWebSocketFrames#4 BinaryFrameHandler 会处理BinaryWebSocketFrames#5 ContinuationFrameHandler 会处理ContinuationWebSocketFrames

如果你想支持安全的WebSocket,你只需要在ChannelPipeline的开始处添加 SslHandler 刘就可以了。

SPDY

对于HTTP来说SPDY (SPeeDY)是一场新的运动。 Google 发明了 SPDY,但是你在将要到来的HTTP 2.0中,你会发现它的一些思想。

SPDY的思想是使得传输更加的快:

  • 压缩所有的东西

  • 加密所有的东西

  • 允许每个连接多个传输

  • 提供了不同的传输优势

截止目前为止存在三个版本的SPDY 。这些版本基于:

  • 草案 1 – 初始版本

  • 草案 2 – 包含修复和服务器端推送

  • 草案 3 – 包含修复和新的特点例如流程控制

许多在目前都支持SPDY ,包括Google Chrome, Firefox, 和Opera。

Netty 支持草案2和草案3,它们在目前使用最多,允许你支持大多数的用户。

这里我们不会介绍SPDY的使用,在本书的后面会有一个完整的章节会介绍。

处理空闲连接和超时

你有时候会发现你需要处理空闲连接和超时。你可以通过心跳来确定远程端点是否还存在。另一种方式是断开连接如果空闲了太长时间。处理空闲连接在许多应用中是核心部分,Netty提供了这一解决方案。存在三种不同的ChannelHandlers 来处理空闲和超时,如表格8.4所示。

表格 8.4 空闲和超时处理器

|名称| 描述|

| ————- | —–:|

|IdleStateHandler|当连接空闲很长时间的时候,IdleStateHandler触发 IdleStateEvent 事件,你可以在IdleStateEvent中进行处理|

|ReadTimeoutHandler|当流入的数据接收超时的时候,ReadTimeoutHandler 会抛出ReadTimeoutException异常,并且关闭通道|

|WriteTimeoutHandler|当很长时间没有接收到流入的数据时,WriteTimeoutHandler 抛出 WriteTimeoutException ,并且关闭通道|

在实际中使用最多的是IdleStateHandler,下一步我们将聚焦于它。

列表8.9展示如何使用 IdleStateHandler,在60秒内没有接收也没有发送数据的时候被通知。在这种场景心跳会被写入远程,如果失败则会关闭连接。

Listing 8.9 空闲的时候发送心跳

public class IdleStateHandlerInitializer extends ChannelInitializer<Channel>{@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS)); #1pipeline.addLast(new HeartbeatHandler());}public static final class HeartbeatHandler extendsChannelStateHandlerAdapter {private static final ByteBuf HEARTBEAT_SEQUENCE =Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEAT", CharsetUtil.ISO_8859_1)); #2@Overridepublic void userEventTriggered(ChannelHandlerContext ctx,Object evt) throws Exception {if (evt instanceof IdleStateEvent) {ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); #3} else {super.userEventTriggered(ctx, evt); #4}}}}#1 添加IdleStateHandler 会在超出60秒没有写和读的时候触发IdleStateEvent事件#2要发送出去的心跳#3 发送心跳,如果失败关闭连接#4 不是IdleStateEvent类型,传递给 ChannelPipeline中的下一个处理器

基于分隔符和长度协议的解码

当你使用Netty的时候,你会遇到基于分隔符和基于长度的协议,你需要进行解码。这一部分将介绍Netty中这些协议的实现。

基于分隔符的协议

O你经常需要处理基于分隔符的协议或者在它的基础上构建。有很多基于分隔符的例子,例如: SMTP, POP3, IMAP, 和 Telnet等等。对于这些,Netty提供了一些特殊的处理器,使的被一些序列分割的帧很容易提取。

表格 8.5 ChannelHandler 来处理空闲和超时

|名称| 描述|

| ————- | —–:|

|DelimiterBasedFameDecoder|对于一个给定的分隔符进行提取帧的解码器。|

|LineBasedFrameDecoder|根据 \r \n进行帧提取的解码器 。它要比DelimiterBasedFrameDecoder要快。|

它是如何工作,让我们来看一看图8.5,它介绍了”\r\n“ 分割的帧的处理。

分隔符终结的帧处理

图 8.5 分隔符终结的帧处理

#1 字节流#2第一个从流中提取的帧#3 第一个从流中提取的帧

列表 8.10展示如何使用LineBasedFrameDecoder提取 “\r\n”分割的帧。

列表 8.10 处理 \r\n 分割的帧

public class LineBasedHandlerInitializer extends ChannelInitializer<Channel>{@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LineBasedFrameDecoder(65 * 1024)); #1pipeline.addLast(new FrameHandler()); #2}public static final class FrameHandlerextends SimpleChannelInboundHandler<ByteBuf> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,ByteBuf msg) throws Exception { #3// Do something with the frame}}}#1添加LineBasedFrameDecoder 来抽取帧,并且过滤到Pipeline中的下一个处理器。#2 添加 FrameHandler来接收帧#3 使用帧来做些事情

如果你的帧不是使用换行符分割的,你可以使用DelimiterBasedFrameDecoder 采用类似的方式处理。你只需要将分隔符传递给构造器。如果你想实现你自己的基于分隔符的协议,这些解码器也很有用。假设你有仅仅处理命令的协议。这些命令的形式是名称和参数。名称和参数以空格分隔。

如果你继承 LineBasedFrameDecoder,写个解码器是很容易的事情了。列表 8.11 展示了如何完成。

列表 8.11 命令解码器和处理器

public class CmdHandlerInitializer extends ChannelInitializer<Channel> {@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new CmdDecoder(65 * 1024)); #1pipeline.addLast(new CmdHandler()); #2}public static final class Cmd { #3private final ByteBuf name;private final ByteBuf args;public Cmd(ByteBuf name, ByteBuf args) {this.name = name;this.args = args;}public ByteBuf name() {return name;}public ByteBuf args() {return args;}}public static final class CmdDecoder extends LineBasedFrameDecoder {public CmdDecoder(int maxLength) {super(maxLength);}@Overrideprotected Object decode(ChannelHandlerContext ctx, ByteBuf buffer)throws Exception {ByteBuf frame = (ByteBuf) super.decode(ctx, buffer); #4if (frame == null) {return null; #5}int index = frame.indexOf(frame.readerIndex(),frame.writerIndex(), (byte) ' '); #6return new Cmd(frame.slice(frame.readerIndex(), index),frame.slice(index +1, frame.writerIndex())); #7}}public static final class CmdHandlerextends SimpleChannelInboundHandler<Cmd> {@Overridepublic void channelRead0(ChannelHandlerContext ctx, Cmd msg)throws Exception {// Do something with the command #8}}}#1 添加CmdDecoder 来抽取命令,并将命令传递给Pipeline中的下一个处理器。#2 添加CmdHandler 来接收命令。#3 代表命令的Pojo #4 抽取由 \r\n分割的 ByteBuf #5 如果没有完整的帧直接返回null  #6 锁定第一个空格,因为它是名称和参数的分隔符#7 构造命

基于长度的协议

经常你会发现基于长度的协议。经常在这种情况下,Netty提供了两种不同的解码器来帮你抽取帧,例如表格8.6所示。

表格 8.6 基于长度提取帧的解码

|名称| 描述|

| ————- | —–:|

|FixedLengthFrameDecoder| 抽取相同固定长度的解码|

|LengthFieldBasedFrameDecoder| 基于长度编码在帧头部中的解码|

为了更清晰的说明这些,通过示意图说明它们。图8.6展 FixedLengthFrameDecoder如何工作。

8字节的固定长度的消息

图 8.6 固定长度为8的消

#1字节流 #2提取长度为8的帧

像展示的那样 ,FixedLengthFrameDecoder提取长度固定的帧,在这个例子中长度是8。

在更多的场景中,你会发现帧的长度编码在帧的头部中。为了达到这个目的,你可以使用LengthFieldBasedFrameDecoder, 它将从头部中读取长度,根据这个长度提取帧。图8.5展示了如何工作。

头部包含固定长度的消息

图 8.7 固定长度编码在头部的消息

#1 长度编码在帧头部#2 帧的内容中包含编码的长#3 抽取的帧中去除了长度信

如果长度字段是提取的头部帧的一部分,这可以通过LengthFieldBasedFrameDecoder的构造函数配置,它允许你指定长度字段在帧中的位置,以及长度。它很灵活,需要更多的信息,请参照API文档。

因为FixedLengthFrameDecoder很容易使用,我们关注 LengthFieldBasedFrameDecoder的使用。

列表8.12展示了如何使用使用LengthFieldBasedFrameDecoder 来抽取帧的长度编码在前8个字节中的帧。

列表 8.12 命令的解码器和处理器

public class LengthBasedInitializer extends ChannelInitializer<Channel> {@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LengthFieldBasedFrameDecoder(65 * 1024, 0, 8)); #1pipeline.addLast(new FrameHandler()); #2}public static final class FrameHandlerextends SimpleChannelInboundHandler<ByteBuf> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,ByteBuf msg) throws Exception {// Do something with the frame #3}}}#1 添加LengthFieldBasedFrameDecoder 来抽取存在编码长度的#2 添加FrameHandler来处理帧#3使用这些帧来做些事情

写大的数据

对于异步框架来说,高效的写大块数据通常存在问题,因为如果网络不好,你需要停止写入,否则的会导致内存不足。Netty允许你写文件的时候使用零拷贝的方式,它在内核空间中处理了从文件系统到网络栈中的缓慢过程,从而使的性能达到最高。

这只会在你的传输的内容不修改的情况下起作用。如果修改了,Netty需要将数据拷贝到用户空间进行操作。所有的这些发生在Netty的核心中,所以你不需要担心它。通过将DefaultFileRegion写入Channel, ChannelHandlerContext, 或者 ChannelPipeline, 将文件内容通过零拷贝写入,如列表8.13所示。

列表 8.13 使用FileRegion传输文件内容

FileInputStream in = new FileInputStream(file); #1FileRegion region = new DefaultFileRegion(in.getChannel(), 0, file.length()); #2channel.writeAndFlush(region).addListener(new ChannelFutureListener() { #3@Overridepublic void operationComplete(ChannelFuture future)throws Exception {if (!future.isSuccess()) {Throwable cause = future.cause(); #4// Do something}}});#1 获取文件的FileInputStream #2 创建新的 DefaultFileRegion ,起始位置是0,结束位置为文件的结#3 发送 DefaultFileRegion 注册一个ChannelFutureListener#4在发送的过程中处理错误

但是如果你不想发送文件,而是一些大块的数据怎么办?

Netty有一个特殊的处理器,叫做 ChunkedWriteHandler,它允许你处理大块的数据,是通过 处 ChunkedInput 实现来完成。表格8.7展示了这些实现。

表格 8.7 提供的 ChunkedInput 实现*

|名称| 描述|

| ————- | —–:|

|ChunkedFile|ChunkedInput 实现允许你写入文件 (在你的平台不支持零内存拷贝的时候使用).|

|ChunkedNioFile|ChunkedInput 实现允许你写入文件 (在你的平台不支持零内存拷贝的时候使用).|

|ChunkedNioStream| ChunkedInput 实现允许你从ReadableByteChannel中传输内容|

|ChunkedStream|ChunkedInput 实现允许你从InputStream中传输内容.|

ChunkedStream实现是使用最多的,所以我会在列表8.14 展示它的使用。

列表 8.14 使用FileRegion传输文件内容

public class ChunkedWriteHandlerInitializerextends ChannelInitializer<Channel> {private final File file;public ChunkedWriteHandlerInitializer(File file) {this.file = file;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ChunkedWriteHandler()); #1pipeline.addLast(new WriteStreamHandler()); #2}public final class WriteStreamHandlerextends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx)throws Exception {super.channelActive(ctx);ctx.writeAndFlush(new ChunkedStream(new FileInputStream(file))); #3}}}#1 添加ChunkedWriteHandler 来处理ChunkedInput实现#2 添加WriteStreamHandler 来写ChunkedInput#3 在连接建立之后,通过ChunkedStream 写文件内容 (这里以FileInputStream 为例,任何的 InputStream 都可以)

序列化数据

当你想在网络上传输 POJOs ,并想在远程端点操作它,

Java 提供了ObjectOutputStream 和ObjectInputStream ,和它的序列化标记接口。但是还有其他的方式可以做这些事情。本部分介绍Netty为我们提供的这些东西。

通过简单的 JDK序列化

如果你要和使用ObjectOutputStream 和

ObjectInputStream的同伴交流,你需要保持兼容性,或者你不想使用外部的依赖, JDK序列化是很好的选择。

表格 8.8 提供的JDK 序列化 codec

|名称| 描述|

| ————- | —–:|

|CompatibleObjectDecoder|使用简单的 JDK序列化解码,可以和没有使用Netty的伙伴一块使用 |

|CompatibleObjectEncoder| 使用简单的 JDK序列化编码,可以和没有使用Netty的伙伴一块使用|

|CompactObjectDecoder|使用定制的基于JDK的序列化进行解码。如果你想增加速度,但不想增加依赖的时候使用它。大多数情况下其他的序列化技术更被认可。|

|CompactObjectEncoder|使用定制的基于JDK的序列化进行编码。如果你想增加速度,但不想增加依赖的时候使用它。大多数情况下其他的序列化技术更被认可。|

通过JBoss Marshalling进行序列化

如果你可以考虑额外的依赖,JBoss Marshalling 是不错的选择。它比JDK的序列化快三倍,更加紧凑。

Netty存在与使用JDK的同伴相兼容的实现,还包含快速的 JBoss Marshalling来实现。表格 8.9 阐述了JBoss Marshalling codecs。

表格 8.9 JBoss Marshalling codec

|名称| 描述|

| ————- | —–:|

|CompatibleMarshallingDecoder|使用 JDK 序列化进行解码,所以可以和不使用Netty的兼容使用|

|CompatibleMarshallingEncoder| |使用 JDK 序列化进行编码,所以可以和不使用Netty的兼容使用|

|MarshallingDecoder| MarshallingDecoder使用定制的序列化进行解码;MarshallingEncoder使用定制的序列化进行编码。|

列表 8.15 使用 JBoss Marshalling

public class MarshallingInitializer extends ChannelInitializer<Channel> {private final MarshallerProvider marshallerProvider;private final UnmarshallerProvider unmarshallerProvider;public MarshallingInitializer(UnmarshallerProvider unmarshallerProvider,MarshallerProvider marshallerProvider) {this.marshallerProvider = marshallerProvider;this.unmarshallerProvider = unmarshallerProvider;}@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();pipeline.addLast(new MarshallingDecoder(unmarshallerProvider));pipeline.addLast(new MarshallingEncoder(marshallerProvider));pipeline.addLast(new ObjectHandler());}public static final class ObjectHandlerextends SimpleChannelInboundHandler<Serializable> {@Overridepublic void channelRead0(ChannelHandlerContext channelHandlerContext,Serializable serializable) throws Exception {// Do something}}}

使用 ProtoBuf序列化

Netty中的下一个序列化的解决方案使ProtoBuf。

ProtoBuf 在Google 被开源,提供了一种紧凑,高效的编码,解码方式。它支持各种各样的语言,所以它适合于跨平台项目。 表格 8.10 展示了ProtoBuf 的实现。

表格 8.10 Protobuf codec

|名称| 描述|

| ————- | —–:|

| ProtobufDecoder| 使用ProtoBufs进行解码。|

| ProtobufEncoder|使用ProtoBufs进行编码。|

| ProtobufVarint32FrameDecoder|通过消息中的 Google Protocol BuffersBase 128 Varints 整数长度字段进行动态的分割接收到的ByteBufs |

列表 8.16 使用 Google Protobuf

public class ProtoBufInitializer extends ChannelInitializer<Channel> {private final MessageLite lite;public ProtoBufInitializer(MessageLite lite) {this.lite = lite;}@Overrideprotected void initChannel(Channel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ProtobufVarint32FrameDecoder()); #1pipeline.addLast(new ProtobufEncoder()); #2pipeline.addLast(new ProtobufDecoder(lite)); #3pipeline.addLast(new ObjectHandler()); #4}public static final class ObjectHandlerextends SimpleChannelInboundHandler<Object> {@Overridepublic void channelRead0(ChannelHandlerContext ctx, Object msg)throws Exception {// Do something with the object}}}#1 添加ProtobufVarint32FrameDecoder来分割#2 添加 ProtobufEncoder 来处理编码消息#3 添加 ProtobufDecoder 来解码消息#4 添加ObjectHandler 来处理解码后的消息

你可以看到,使用 Protobuf很容易。 在ChannelPipeline中添加合适的处理器就行了。

总结

本章简单的介绍了你可以使用的一些codecs 和handlers ,你可以在你的应用中复用。这样你就可以你需要的东西,从而避免一些重复操作。

你可以同样知道如何组合不同的 codecs/handlers 来构建你自己的逻辑,你同样可以继承已有的实现来调整需要的逻辑。

复用Netty提供的部分的另外的一个优点是它经过了许多用户的测试,很健壮。

本章仅仅包含Netty提供的经常使用的codecs 和 handlers 。还有更多,如果需要包含全部,需要一个很大的章节。请参考Netty的API文档来获取其他部分。

在下一章节,将介绍如何引导你的服务,以及组合处理器来运行。

0 0
原创粉丝点击