一、前言

  前面学习了Netty的codec框架,下面接着学习ChannelHandler与codec之间的关联。

二、ChannelHandler和codec

  Netty为不同的协议提供了处理器和编解码器,可以开箱即用,这些工具支持SSL / TLS和WebSocket,以及通过数据压缩使得HTTP有更好的性能。

  2.1 使用SSL/TLS保护Netty应用程序

  由于数据隐私非常重要,而SSL和TLS等加密协议用于处理数据隐私,这些协议在其他协议之上以实现数据安全性。安全网站会使用这种协议,同时,不基于HTTP的应用程序,如安全SMTP(SMTPS)邮件服务甚至关系数据库系统也会用到这些协议。

  为支持SSL/TLS协议,Java提供了javax.net.ssl包,其SSLContext和SSLEngine类使得实现解密和加密变得相当直接容易。而Netty使用SslHandler的ChannelHandler实现,其内部是使用SSLEngine来进行实际工作。

  下图展示了通过SslHandler的数据流。

  

  下面代码展示如何通过ChannelInitializer将SslHandler添加至ChannelPipeline中。 

  1. public class SslChannelInitializer extends ChannelInitializer<Channel>{
  2. private final SslContext context;
  3. private final boolean startTls;
  4.  
  5. public SslChannelInitializer(SslContext context,
  6. boolean startTls) {
  7. this.context = context;
  8. this.startTls = startTls;
  9. }
  10. @Override
  11. protected void initChannel(Channel ch) throws Exception {
  12. SSLEngine engine = context.newEngine(ch.alloc());
  13. ch.pipeline().addFirst("ssl",
  14.   new SslHandler(engine, startTls));
  15. }
  16. }

  在大多数情况下,SslHandler都是ChannelPipeline中的第一个ChannelHandler,只有当所有其他ChannelHandler将其逻辑应用于数据之后,才能进行加密操作。

  2.2 构建Netty的HTTP/HTTPS应用程序

  HTTP/HTTPS是最常见的协议套件之一,许多Web Service API都是基于HTTP/HTTPS的。

  1. HTTP的decoder、encoder、codec

  HTTP基于请求/响应模式:客户端发送HTTP请求至服务端,服务端响应HTTP请求至客户端。Netty提供各种编码器和解码器以简化使用此协议。

  下图展示了HTTP请求的组成部分。

  

  而下图展示了HTTP响应的组成部分。

  

  下面代码的HttpPipelineInitializer可以为应用提供HTTP支持,仅仅只需要往ChannelPipeline中添加正确的ChannelHandler即可。 

  1. public class HttpPipelineInitializer extends ChannelInitializer<Channel> {
  2. private final boolean client;
  3. public HttpPipelineInitializer(boolean client) {
  4. this.client = client;
  5. }
  6. @Override
  7. protected void initChannel(Channel ch) throws Exception {
  8. ChannelPipeline pipeline = ch.pipeline();
  9. if (client) {
  10. pipeline.addLast("decoder", new HttpResponseDecoder());
  11. pipeline.addLast("encoder", new HttpRequestEncoder());
  12. } else {
  13. pipeline.addLast("decoder", new HttpRequestDecoder());
  14. pipeline.addLast("encoder", new HttpResponseEncoder());
  15. }
  16. }
  17. }

  2. HTTP消息聚合

  当管道中的处理器被初始化后,就可以处理不同的消息,但由于HTTP请求和响应由多部分组成,需要将这些部分汇聚成完整的结果,Netty提供了一个合成器,其可以消息的不同部分组合成FullHttpRequest和FullHttpResponse。由于消息在完整之前需要被缓存,因此其会带来小小的开销,但是不用担心消息的碎片化处理。

  下面代码展示了如何自动化装配消息。

  1. public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {
  2. private final boolean isClient;
  3. public HttpAggregatorInitializer(boolean isClient) {
  4. this.isClient = isClient;
  5. }
  6. @Override
  7. protected void initChannel(Channel ch) throws Exception {
  8. ChannelPipeline pipeline = ch.pipeline();
  9. if (isClient) {
  10. pipeline.addLast("codec", new HttpClientCodec());
  11. } else {
  12. pipeline.addLast("codec", new HttpServerCodec());
  13. }
  14. pipeline.addLast("aggregator",
  15. new HttpObjectAggregator(512 * 1024));
  16. }
  17. }

  3. HTTP压缩

  使用HTTP时,建议采用压缩来尽可能减少传输数据的大小。Netty为压缩和解压缩提供了ChannelHandler的实现,其支持gzip和deflate编码。

  如下代码展示了如何自动压缩HTTP消息。  

  1. public class HttpCompressionInitializer extends ChannelInitializer<Channel> {
  2. private final boolean isClient;
  3. public HttpCompressionInitializer(boolean isClient) {
  4. this.isClient = isClient;
  5. }
  6. @Override
  7. protected void initChannel(Channel ch) throws Exception {
  8. ChannelPipeline pipeline = ch.pipeline();
  9. if (isClient) {
  10. pipeline.addLast("codec", new HttpClientCodec());
  11. pipeline.addLast("decompressor",
  12. new HttpContentDecompressor());
  13. } else {
  14. pipeline.addLast("codec", new HttpServerCodec());
  15. pipeline.addLast("compressor",
  16. new HttpContentCompressor());
  17. }
  18. }
  19. }

  4. 使用HTTPS

  当混合了SslHandler时就可以使用HTTPS,代码如下。  

  1. public class HttpsCodecInitializer extends ChannelInitializer<Channel> {
  2. private final SslContext context;
  3. private final boolean isClient;
  4. public HttpsCodecInitializer(SslContext context, boolean isClient) {
  5. this.context = context;
  6. this.isClient = isClient;
  7. }
  8. @Override
  9. protected void initChannel(Channel ch) throws Exception {
  10. ChannelPipeline pipeline = ch.pipeline();
  11. SSLEngine engine = context.newEngine(ch.alloc());
  12. pipeline.addFirst("ssl", new SslHandler(engine));
  13. if (isClient) {
  14. pipeline.addLast("codec", new HttpClientCodec());
  15. } else {
  16. pipeline.addLast("codec", new HttpServerCodec());
  17. }
  18. }
  19. }

  5. WebSocket

  Netty为基于HTTP的应用程序提供了支持,WebSocket解决了长期存在的问题:如果底层使用HTTP协议,是一系列请求-响应交互,那么如何实时发布信息。AJAX提供了一些改进,但是数据流的驱动仍然来自客户端的请求。而WebSocket规范及其实现代表了一种更有效的解决方案,其为双向流量提供了一个单一的TCP连接,其在客户端和服务器之间提供真正的双向数据交换,并且可以处理任何类型的数据。

  下图展示了WebSocket协议,开始数据通信为纯HTTP,之后升级到双向WebSocket。

  

  为了应用中添加WebSocket支持,可以在管道中添加WebSocketFrames,其包含如下类型。

  

  下面代码展示了如何使用WebSocketServerProtocolHandler。  

  1. public class WebSocketServerInitializer extends ChannelInitializer<Channel>{
  2. @Override
  3. protected void initChannel(Channel ch) throws Exception {
  4. ch.pipeline().addLast(
  5. new HttpServerCodec(),
  6. new HttpObjectAggregator(65536),
  7. new WebSocketServerProtocolHandler("/websocket"),
  8. new TextFrameHandler(),
  9. new BinaryFrameHandler(),
  10. new ContinuationFrameHandler());
  11. }
  12. public static final class TextFrameHandler extends
  13. SimpleChannelInboundHandler<TextWebSocketFrame> {
  14. @Override
  15. public void channelRead0(ChannelHandlerContext ctx,
  16. TextWebSocketFrame msg) throws Exception {
  17. // Handle text frame
  18. }
  19. }
  20.  
  21. public static final class BinaryFrameHandler extends
  22. SimpleChannelInboundHandler<BinaryWebSocketFrame> {
  23. @Override
  24. public void channelRead0(ChannelHandlerContext ctx,
  25. BinaryWebSocketFrame msg) throws Exception {
  26. // Handle binary frame
  27. }
  28. }
  29.  
  30. public static final class ContinuationFrameHandler extends
  31. SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
  32. @Override
  33. public void channelRead0(ChannelHandlerContext ctx,
  34. ContinuationWebSocketFrame msg) throws Exception {
  35. // Handle continuation frame
  36. }
  37. }
  38. }

  而为了保证WebSocket的安全性,可以将SslHandler作为管道中的第一个处理器。

  2.3 空闲连接和超时

  检测空闲连接和超时对于及时释放资源至关重要,Netty提供了几个处理器进行处理,如IdleStateHandler、ReadTimeoutHandler、WriteTimeoutHandler等。

  首先看看IdleStateHandler的原因,如果60S内没有接受或发送数据,如何接收通知,可以使用向远程对等体发送心跳消息来获取通知; 如果没有响应,则连接被关闭。

  1. public class IdleStateHandlerInitializer extends ChannelInitializer<Channel> {
  2. @Override
  3. protected void initChannel(Channel ch) throws Exception {
  4. ChannelPipeline pipeline = ch.pipeline();
  5. pipeline.addLast(
  6. new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS));
  7. pipeline.addLast(new HeartbeatHandler());
  8. }
  9. public static final class HeartbeatHandler
  10. extends ChannelStateHandlerAdapter {
  11. private static final ByteBuf HEARTBEAT_SEQUENCE =
  12. Unpooled.unreleasableBuffer(Unpooled.copiedBuffer(
  13. "HEARTBEAT", CharsetUtil.ISO_8859_1));
  14. @Override
  15. public void userEventTriggered(ChannelHandlerContext ctx,
  16. Object evt) throws Exception {
  17. if (evt instanceof IdleStateEvent) {
  18. ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate())
  19. .addListener(
  20. ChannelFutureListener.CLOSE_ON_FAILURE);
  21. } else {
  22. super.userEventTriggered(ctx, evt);
  23. }
  24. }
  25. }
  26. }

  2.4 解码限定的和基于长度的协议

  1. 限定的协议

  限定消息协议使用指定的字符来标记称为帧的消息或消息段的开始或结束位置,如如SMTP,POP3,IMAP和Telnet均使用该协议。如下的两个解码器用于处理限定的和基于长度的协议。

  

  下图显示了如何以行尾序列\ r \ n(回车+换行)分隔符来处理帧。

  

  如下代码展示了如何使用LineBasedFrameDecoder。 

  1. public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
  2. @Override
  3. protected void initChannel(Channel ch) throws Exception {
  4. ChannelPipeline pipeline = ch.pipeline();
  5. pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));
  6. pipeline.addLast(new FrameHandler());
  7. }
  8. public static final class FrameHandler
  9. extends SimpleChannelInboundHandler<ByteBuf> {
  10. @Override
  11. public void channelRead0(ChannelHandlerContext ctx,
  12. ByteBuf msg) throws Exception {
  13. // Do something with the data extracted from the frame
  14. }
  15. }
  16. }

  DelimiterBasedFrameDecoder类可以自定义字符来处理数据。

  2. 基于长度的协议

  基于长度的协议通过在帧的头部对其长度进行编码来定义帧,而不是通过用特殊的分隔符标记其结尾,如下两个类用于处理该协议。

  

  下图展示了FixedLengthFrameDecoder的操作,每帧由8个字节构成。

  

  对于可变长度的帧,可以使用LengthFieldBasedFrameDecoder进行处理,其从头部字段确定帧长度,并从数据流中提取指定的字节数。

  下图展示了长度字段在头部的偏移量0并且具有2字节。

  

  2.5 写大数据

  由于网络原因,异步框架中的写入大量数据将存在问题,因为写入操作是非阻塞的,在写入完成时或者所有的数据都没有被写出就会通知ChannelFuture,此时,若不停止写入数据,可能会造成内存溢出,因此,在写入大量数据时,需要处理与远程对等体连接缓慢可能导致内存释放延迟的情况。FileRegion支持零拷贝文件传输的通道。

  下面是FileRegion的使用示例。  

  1. FileInputStream in = new FileInputStream(file);
  2. FileRegion region = new DefaultFileRegion(
  3. in.getChannel(), 0, file.length());
  4. channel.writeAndFlush(region).addListener(
  5. new ChannelFutureListener() {
  6. @Override
  7. public void operationComplete(ChannelFuture future)
  8. throws Exception {
  9. if (!future.isSuccess()) {
  10. Throwable cause = future.cause();
  11. // Do something
  12. }
  13. }
  14. });

  此示例仅适用于文件内容的直接传输,不包括应用程序对数据的任何处理,若需要将数据从文件系统复制到用户内存,可以使用ChunkedWriteHandler,它支持异步编写大型数据流,而不会导致高内存消耗。

  其核心是ChunkedInput<B>接口,B代码readChunk的返回类型,对于ChunkedInput<B>,有如下的四种实现。

  

  如下是ChunkedStream的使用示例。  

  1. public class ChunkedWriteHandlerInitializer
  2. extends ChannelInitializer<Channel> {
  3. private final File file;
  4. private final SslContext sslCtx;
  5.  
  6. public ChunkedWriteHandlerInitializer(File file, SslContext sslCtx) {
  7. this.file = file;
  8. this.sslCtx = sslCtx;
  9. }
  10. @Override
  11. protected void initChannel(Channel ch) throws Exception {
  12. ChannelPipeline pipeline = ch.pipeline();
  13. pipeline.addLast(new SslHandler(sslCtx.createEngine());
  14. pipeline.addLast(new ChunkedWriteHandler());
  15. pipeline.addLast(new WriteStreamHandler());
  16. }
  17.  
  18. public final class WriteStreamHandler
  19. extends ChannelInboundHandlerAdapter {
  20. @Override
  21. public void channelActive(ChannelHandlerContext ctx)
  22. throws Exception {
  23. super.channelActive(ctx);
  24. ctx.writeAndFlush(
  25. new ChunkedStream(new FileInputStream(file)));
  26. }
  27. }
  28. }

  2.6 序列化数据

  JDK提供了ObjectOutputStream和ObjectInputStream对POJO的原始数据类型和图形进行序列化和反序列化,但其并不高效。

  1. JDK序列化

  当应用程序必须与使用ObjectOutputStream和ObjectInputStream与对等体进行交互时,首先应该考虑兼容性,此时JDK的序列化是正确的选择。下图展示了Netty与JDK进行互操作的序列化类。

  

  2. JBOSS序列化

  JBOSS比JDK序列化快三倍,并且更为紧凑,Netty使用如下两个编解码器来支持JBOSS。

  

  如下代码展示了如何使用JBOSS进行序列化操作。 

  1. public class MarshallingInitializer extends ChannelInitializer<Channel> {
  2. private final MarshallerProvider marshallerProvider;
  3. private final UnmarshallerProvider unmarshallerProvider;
  4. public MarshallingInitializer(
  5. UnmarshallerProvider unmarshallerProvider,
  6. MarshallerProvider marshallerProvider) {
  7. this.marshallerProvider = marshallerProvider;
  8. this.unmarshallerProvider = unmarshallerProvider;
  9. }
  10. @Override
  11. protected void initChannel(Channel channel) throws Exception {
  12. ChannelPipeline pipeline = channel.pipeline();
  13. pipeline.addLast(new MarshallingDecoder(unmarshallerProvider));
  14. pipeline.addLast(new MarshallingEncoder(marshallerProvider));
  15. pipeline.addLast(new ObjectHandler());
  16. }
  17. public static final class ObjectHandler
  18. extends SimpleChannelInboundHandler<Serializable> {
  19. @Override
  20. public void channelRead0(
  21. ChannelHandlerContext channelHandlerContext,
  22. Serializable serializable) throws Exception {
  23. // Do something
  24. }
  25. }
  26. }

  3. Protobuf序列化

  Netty还可使用协议缓冲区进行序列化操作,其由谷歌开发,协议缓冲区以紧凑和高效的方式对结构化数据进行编码和解码,下图显示了Netty为支持protobuf而实现的ChannelHandler。

  

  下面示例展示了如何使用protobuf。 

  1. public class ProtoBufInitializer extends ChannelInitializer<Channel> {
  2. private final MessageLite lite;
  3. public ProtoBufInitializer(MessageLite lite) {
  4. this.lite = lite;
  5. }
  6. @Override
  7. protected void initChannel(Channel ch) throws Exception {
  8. ChannelPipeline pipeline = ch.pipeline();
  9. pipeline.addLast(new ProtobufVarint32FrameDecoder());
  10. pipeline.addLast(new ProtobufEncoder());
  11. pipeline.addLast(new ProtobufDecoder(lite));
  12. pipeline.addLast(new ObjectHandler());
  13. }
  14. public static final class ObjectHandler
  15. extends SimpleChannelInboundHandler<Object> {
  16. @Override
  17. public void channelRead0(ChannelHandlerContext ctx, Object msg)
  18. throws Exception {
  19. // Do something with the object
  20. }
  21. }
  22. }

三、总结

  本篇博文讲解了如何保证Netty应用程序的安全性,以及如何处理不同协议的数据,以及编解码器和ChannelHandler之间的关系,以及如何把编解码器添加至管道中进行处理。也谢谢各位园友的观看~

【Netty】ChannelHandler和codec的更多相关文章

  1. Netty入门(4) - 附带的ChannelHandler和Codec

    使用SSL/TLS创建安全的Netty程序 Java提供了抽象的SslContext和SslEngine,实际上SslContext可以用来获取SslEngine来进行加密和解密.Netty拓展了Ja ...

  2. 【Netty】源码分析目录

    前言 为方便系统的学习Netty,特整理文章目录如下. [Netty]第一个Netty应用 [Netty]Netty核心组件介绍 [Netty]Netty传输 [Netty]Netty之ByteBuf ...

  3. Netty与网络编程

    Netty什么? Netty项目是一个提供异步事件驱动网络应用框架和快速开发可维护的高性能高扩展性服务端和客户端协议工具集的成果.换句话说,Netty是一个NIO客户端服务端框架,它使得快速而简单的开 ...

  4. Netty源码—四、事件处理

    前面经过channel初始化.注册,所需要的数据结构(epoll_event)基本上准备好了,serverSocket也处于监听状态,可以接收来自客户端的请求了.NioServerSocketChan ...

  5. Netty入门(1) - 简介

    什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Tomcat和Netty有什么区别? Netty和Tom ...

  6. Netty入门教程——认识Netty

    什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...

  7. Netty入门1之----认识Netty

      Netty 什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java ...

  8. 使用Netty实现通用二进制协议的高效数据传输

    Netty是一个高性能的NIO通信框架,提供异步的.事件驱动的网络编程模型.使用Netty可以方便用户开发各种常用协议的网络程序.例如:TCP.UDP.HTTP等等. Netty的最新版本是3.2.7 ...

  9. netty实现消息中心(二)基于netty搭建一个聊天室

    前言 上篇博文(netty实现消息中心(一)思路整理 )大概说了下netty websocket消息中心的设计思路,这篇文章主要说说简化版的netty聊天室代码实现,支持群聊和点对点聊天. 此demo ...

随机推荐

  1. oralce set

    1         SET TIMING ON 说明:显示SQL语句的运行时间.默认值为OFF. 在SQLPLUS中使用,时间精确到0.01秒.也就是10毫秒. 在PL/SQL DEVELOPER 中 ...

  2. C++中的继承详解(3)作用域与重定义,赋值兼容规则

    作用域与同名隐藏 一样的,先上代码 1 class A 2 { 3 public: 4 int a_data; 5 void a() 6 { 7 cout << "A" ...

  3. C#设计模式之简单工厂模式

    简单工厂模式解释:  简单工厂模式(Simple Factory Pattern)属于类的创新型模式,又叫静态工厂方法模式(Static FactoryMethod Pattern) 是通过专门定义一 ...

  4. 配置WampServer以及搭建WordPress的一些问题,持续总结。

    这里用的版本是Wampserver2.4-x64. Wamp的安装就不赘述了,一路点通过就可以了. #注意:(最好别改,省的麻烦) 80端口是Apache 的默认端口,在httpd.conf文件中配置 ...

  5. css系列:input的placeholder在safari下设置行高失效

    在项目中遇到input的placeholder在safari下设置行高失效的问题,问度娘后未得治原因,倒是有解决办法: 方法一:使用padding使提示文字居中,如果font-size:14px,UI ...

  6. Linux - PCB之task_struct结构体

     task_struct结构描述  1. 进程状态(State) 进程执行时,它会根据具体情况改变状态 .进程状态是调度和对换的依据.Linux中的进程主要有如下状态,如表4.1所示. 内核表示 含义 ...

  7. js日期转化(计算一周的日期)

    之前做项目的时候遇到过一个日期转化的问题,一个日期控件和近一天,近七天和近一月的的联动效果.发现自己不会,后来就百度了一下解决了这个问题. 现在抽空又写了一个时间转化的案例(计算一周的日期),因为之前 ...

  8. iOS开发 - CocoaPods安装和使用教程

    一.CocoaPods简介 1.什么是CocoaPods CocoaPods是iOS的包管理工具. 2.为什么要使用CocoaPods 在开发iOS项目时,经常会使用第三方开源库,手动引入流程复杂,并 ...

  9. 蓝桥杯- 移动距离-java

    /* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...

  10. poj1410计算几何线段相交

    You are to write a program that has to decide whether a given line segment intersects a given rectan ...