一、前言

Netty 为许多通用协议提供了编解码器和处理器,几乎可以开箱即用, 这减少了你在那些相当繁琐的事务上本来会花费的时间与精力。另外,这篇文章中,就不涉及 Netty 对 WebSocket协议 的支持了,因为涉及的篇幅有点大,会在下一篇文章做一个具体的介绍。

二、SSL 协议

SSL 协议是安全协议,层叠在其他协议之上。为了支持 SSL/TLS, Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得实现解密和加密相当简单直接。 Netty 通过一个名为 SslHandler 的 ChannelHandler 实现利用了这个 API, 其中 SslHandler 在内部使用 SSLEngine 来完成实际的工作。下图描述的是 SslHandler 的数据流。

  1. @Override
  2. protected void initChannel(Channel ch) throws Exception {
  3. ByteBufAllocator byteBufAllocator = ch.alloc();
  4. //对于每个 SslHandler 实例,都使用 Channel 的 ByteBufAllocator 从 SslContext 获取一个新的 SSLEngine
  5. SSLEngine sslEngine = context.newEngine(byteBufAllocator);
  6. //服务器端模式,客户端模式设置为true
  7. sslEngine.setUseClientMode(false);
  8. //不需要验证客户端,客户端不设置该项
  9. sslEngine.setNeedClientAuth(false);
  10. //要将 SslHandler 设置为第一个 ChannelHandler。这确保了只有在所有其他的 ChannelHandler 将他们的逻辑应用到数据之后,才会进行加密。
  11. //startTls 如果为true,第一个写入的消息将不会被加密(客户端应该设置为true)
  12. ch.pipeline().addFirst("ssl",new SslHandler(sslEngine, startTls));
  13. }

tips:对于 ChannelPipeline 链中 ChannelHandler 执行的顺序 —— 入站事件顺序执行、出站事件逆序执行。

三、HTTP 协议

HTTP 是基于请求/响应模式的:客户端向服务器发送一个 HTTP 请求,然后服务器将会返回一个 HTTP 响应。 下图展示了 Netty 中 HTTP请求和响应的组成部分:

    

Netty 对 HTTP 协议的支持主要提供了以下 ChannelHandler:

HttpResponseDecoder:解码器,用于客户端,解码来自服务端的响应。
HttpRequestEncoder:编码器,用户客户端,编码向服务端发送的请求。
HttpRequestDecoder:解码器,用于服务端,解码来自客户端的请求。
HttpResponseEncoder:编码器,用于服务端,编码向客户端的响应。
HttpClientCodec:编解码器,用户客户端,效果等于 HttpResponseDecoder + HttpRequestEncoder。
HttpServerCodec:编解码器,用户服务端,效果等于 HttpRequestDecoder + HttpResponseEncoder。
HttpObjectAggregator:聚合器,由于 HTTP 的请求和响应可能由许多部分组成,需要聚合它们以形成完整的消息,HttpObjectAggregator 可以将多个消息部分合并为 FullHttpRequest 或者 FullHttpResponse 消息。
HttpContentCompressor:压缩,用户服务端,压缩要传输的数据,支持 gzip 和 deflate 压缩格式。
HttpContentDecompressor:解压缩,用于客户端,解压缩服务端传输的数据。

  1. @Override
  2. protected void initChannel(Channel ch) throws Exception {
  3. ChannelPipeline pipeline = ch.pipeline();
  4. SSLEngine sslEngine = sslContext.newEngine(ch.alloc());
  5. if (isClient) {
  6. //使用 HTTPS,添加 SSL 认证
  7. pipeline.addFirst("ssl", new SslHandler(sslEngine, true));
  8. pipeline.addLast("codec", new HttpClientCodec());
  9. //1、建议开启压缩功能以尽可能多地减少传输数据的大小
  10. //2、客户端处理来自服务器的压缩内容
  11. pipeline.addLast("decompressor", new HttpContentDecompressor());
  12. }else {
  13. pipeline.addFirst("ssl", new SslHandler(sslEngine));
  14. //HttpServerCodec:将HTTP客户端请求转成HttpRequest对象,将HttpResponse对象编码成HTTP响应发送给客户端。
  15. pipeline.addLast("codec", new HttpServerCodec());
  16. //服务端,压缩数据
  17. pipeline.addLast("compressor", new HttpContentCompressor());
  18. }
  19. //目的多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
  20. //将最大的消息为 512KB 的HttpObjectAggregator 添加到 ChannelPipeline
  21. //在消息大于这个之后会抛出一个 TooLongFrameException 异常。
  22. pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
  23. }

tips:当使用 HTTP 时,建议开启压缩功能以尽可能多地减小传输数据的大小。虽然压缩会带来一些 CPU 时钟周期上的开销。

四、拆包和粘包的解决方案

TCP 传输过程中,客户端发送了两个数据包,而服务端却只收到一个数据包,客户端的两个数据包粘连在一起,称为粘包;

TCP 传输过程中,客户端发送了两个数据包,服务端虽然收到了两个数据包,但是两个数据包都是不完整的,或多了数据,或少了数据,称为拆包;

发生TCP粘包、拆包主要是由于下面一些原因:

1、应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
2、应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
3、进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
4、接收方法不及时读取套接字缓冲区数据,这将发生粘包。

Netty 预定义了一些解码器用于解决粘包和拆包现象,其中大体分为两类:

基于分隔符的协议:在数据包之间使用定义的字符来标记消息或者消息段的开头或者结尾。这样,接收端通过这个字符就可以将不同的数据包拆分开。
基于长度的协议:发送端给每个数据包添加包头部,头部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包头部的长度字段,便知道每一个数据包的实际长度了。

基于分隔符的协议

        

  1. public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
  2.  
  3. @Override
  4. protected void initChannel(Channel ch) throws Exception {
  5. ch.pipeline().addLast(
  6. // 将提取到的桢转发给下一个Channelhandler
  7. new LineBasedFrameDecoder(64 * 1024),
  8. // 添加 FrameHandler 以接收帧
  9. new FrameHandler()
  10. );
  11. }
  12.  
  13. public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
  14.  
  15. @Override
  16. protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
  17. //Do something with the data extracted from the frame
  18. }
  19. }
  20. }

基于长度的协议

LengthFieldBasedFrameDecoder 是 Netty 基于长度协议解决拆包粘包问题的一个重要的类,主要结构就是 header+body 结构。我们只需要传入正确的参数就可以发送和接收正确的数据,那吗重点就在于这几个参数的意义。下面我们就具体了解一下这几个参数的意义。先来看一下LengthFieldBasedFrameDecoder主要的构造方法:

  1. public LengthFieldBasedFrameDecoder(
  2. int maxFrameLength,
  3. int lengthFieldOffset, int lengthFieldLength,
  4. int lengthAdjustment, int initialBytesToStrip)

maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃。
lengthFieldOffset:长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,需要后移几个字节才是长度域。
lengthFieldLength:长度域字节数。用几个字节来表示数据长度。
lengthAdjustment:数据长度修正。因为长度域指定的长度可以使 header+body 的整个长度,也可以只是body的长度。如果表示header+body的整个长度,那么我们需要修正数据长度。
initialBytesToStrip:跳过的字节数。如果你需要接收 header+body 的所有数据,此值就是0,如果你只想接收body数据,那么需要跳过header所占用的字节数。

  1. public class LengthBasedInitializer extends ChannelInitializer<Channel> {
  2.  
  3. @Override
  4. protected void initChannel(Channel ch) throws Exception {
  5. ch.pipeline().addLast(
  6. new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8),
  7. new FrameHandler()
  8. );
  9. }
  10.  
  11. public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
  12.  
  13. @Override
  14. protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
  15. //处理桢的数据
  16. }
  17. }
  18. }

tips:UDP协议不会发生沾包或拆包现象, 因为UDP是基于报文发送的,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开。

五、其他

由于网络饱和的可能性,如何在异步框架中高效地写大块的数据是一个特殊的问题。Netty 通过一个 FileRegion 接口来实现,其在 Netty 的API 文档中的定义是:"通过支持零拷贝的文件传输的 Channel 来发送的文件区域"。但是该接口只适用于文件内容的直接传输,不包括应用程序对文件数据的任何处理。

如果大块的数据要从文件系统复制到用户内存中时,可以安装一个 ChunkedWriteHandler,并用 ChunkedInput 实现写入文件数据。 它支持异步写大型数据流,而又不会导致大量的内存消耗。

  1. public class ChunkedWriteHandlerInitializer extends ChannelInitializer<Channel> {
  2. private final File file;
  3. private final SslContext sslCtx;
  4.  
  5. public ChunkedWriteHandlerInitializer(File file, SslContext sslCtx) {
  6. this.file = file;
  7. this.sslCtx = sslCtx;
  8. }
  9. @Override
  10. protected void initChannel(Channel ch) throws Exception {
  11. ch.pipeline().addLast(
  12. new SslHandler(sslCtx.newEngine(ch.alloc())),
  13. // 添加 ChunkedWriteHandler 以处理作为 ChunkedInput 传入的数据
  14. new ChunkedWriteHandler(),
  15. new WriteStreamHandler()
  16. );
  17. }
  18. private final class WriteStreamHandler extends ChannelHandlerAdapter {
  19. //当连接建立时,channelActive() 方法将使用 ChunkedInput 写文件数据
  20. @Override
  21. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  22. super.channelActive(ctx);
  23. ctx.writeAndFlush(new ChunkedStream(new FileInputStream(file)));
  24. }
  25. }
  26. }

ChunkedWriteHandlerInitializer.java

Netty提供的用于和JDK进行互操作的序列化类 :

Netty提供的用于和 JBoss Marshalling 进行互操作的序列化类 :

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

MarshallingInitializer.java

Netty提供的用于和 Protocol Buffers 进行互操作的序列化类 :

参考资料:《Netty IN ACTION》

演示源代码:https://github.com/JMCuixy/NettyDemo/tree/master/src/main/java/org/netty/demo/protocol

Netty 系列七(那些开箱即用的 ChannelHandler).的更多相关文章

  1. 7. 彤哥说netty系列之Java NIO核心组件之Selector

    --日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第七篇. 简介 上一章我们一起学习了Java NIO的核心组件Buffer,它通常跟Channel一起使用,但是它们在网络IO中又该如何 ...

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

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

  3. WCF编程系列(七)信道及信道工厂

    WCF编程系列(七)信道及信道工厂   信道及信道栈 前面已经提及过,WCF中客户端与服务端的交互都是通过消息来进行的.消息从客户端传送到服务端会经过多个处理动作,在WCF编程模型中,这些动作是按层 ...

  4. SQL Server 2008空间数据应用系列七:基于Bing Maps(Silverlight) 的空间数据展现

    原文:SQL Server 2008空间数据应用系列七:基于Bing Maps(Silverlight) 的空间数据展现 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft ...

  5. C语言高速入口系列(七)

    C语言高速入口系列(七) C语言指针进阶 本章引言: 在前面第5节中我们对C语言的指针进行了初步的学习理解;作为C语言的灵魂, C指针肯定没那么简单,在这一节中,我们将会对指针进行进一步的学习,比方二 ...

  6. 7.oracle学习门户系列七---网络管理和配置

    oracle学习门户系列七 网络管理和配置 们学习了模式和用户.包含模式定义以及模式的作用. 这篇我么来看下ORACLE数据库中的网络管理和配置.只是这篇好像和上篇没有继承啊.这怎么看? Ok,事实上 ...

  7. Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)

    Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 本篇主要讲解iOS开发中的网络监控 前言 在开发中,有时候我们需要获取这些信息: 手机是否联网 ...

  8. java多线程系列(七)---Callable、Future和FutureTask

    Callable.Future和FutureTask 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量 ...

  9. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

随机推荐

  1. css浮动(float)及清除浮动的几种实用方法

    CSS浮动是现在网页布局中使用最频繁的效果之一,而浮动可以帮我们解决很多问题,那么就让我们一起来看一看如何使用浮动. 一.css浮动(float) (1)html文档流 自窗体自上而下分成一行一行,并 ...

  2. JAVA对象 向上转型upcasting,向下转型downcasting

    1:向上转型 向上转型,就是java多态中的父类引用指向子类对象.但要注意的是 父类引用不可以访问子类新增加的成员(属性和方法) 代码举例: /** * 定义一个父类 */ public class ...

  3. Spring Boot 主类及目录结构介绍

    Spring Boot 与传统项目最大的区别是,传统项目都是打成 WAR 包部署到服务器上面,需要额外的 Servlet 容器, 而 Spring Boot 则可以直接打成 jar 包,并内置集成了 ...

  4. 【Spark调优】内存模型与参数调优

    [Spark内存模型] Spark在一个executor中的内存分为3块:storage内存.execution内存.other内存. 1. storage内存:存储broadcast,cache,p ...

  5. 网站后台搭建--springboot项目是如何创建的

    在创建项目之前先说一下ide的问题,从学习软件开始一直到一个月之前,开发用的IDE都是Eclipse,对,就是这个远古时代的开发工具,在使用过程中虽然总是遇到各种bug,但内心里还是存在着一丝理解的想 ...

  6. IntelliJ IDEA 与 SVN配置

    问题背景 如果开发工具使用的是IntelliJ IDEA,版本管理工具使用的是SVN. 就涉及到SVN集成的问题,但是可能会遇到选择在IDEA中配置SVN的时候,在SVN的安装bin目录找不到文件 s ...

  7. maven配置阿里云中央仓库

    首先查看下maven安装位置下的/conf/settings.xml的路径,如下图我这里是D:\Java\apache-maven-3.3.9\conf\settings.xml 然后根据路径找到配置 ...

  8. 一文让你彻底理解 Java NIO 核心组件

    背景知识 同步.异步.阻塞.非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下. 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节). 异步:相对于同 ...

  9. mysql 开发基础系列21 事务控制和锁定语句(下)

    1.  隐含的执行unlock tables 如果在锁表期间,用start transaction命令来开始一个新事务,会造成一个隐含的unlock tables 被执行,如下所示: 会话1 会话2 ...

  10. 【转载】“宇宙最强” IDE,Visual Studio 2019 正式发布

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 本文由葡萄城翻译并发布 今天凌晨Visual Studio 2019已经正式发布,现在已经可以下载了.使用V ...