前言java
怎样才能说本身懂Netty ? 如何将Netty了解到必定的深度 ? 如何全面玩转Netty的实战 ? 不妨沿着下面的路径,对Netty进行一个全面的认识和思考!算法
第一节、网络编程NIO的Reactor模式数据库
第二节、Netty和选择Netty的理由
编程
第三节、Netty入门中三个基本特性
数组
第四节、核心概念和机制 - EventLoop、EventLoopGroup
缓存
第五节、主要组件ChannelHandler、ChannelHandlerContext和ChnnelPipeline
安全
第六节、Netty支持的网络通信传输模式
服务器
第七节、操做系统层ChannelOption详解
网络
第八节、特有的缓冲封装ByteBuf详解
多线程
第九节、Netty中的重要机制引用计数
第十节、Netty如何解决粘包/半包问题
第十一节、Netty重量级组件编解码器
第十二节、Netty内置及引入外部序列和反序列化部件
第十三节、如何独立进行ChannelHandler的单元测试
全面认识Netty开始
第一节、网络编程NIO的Reactor模式
Reactor反应器中的 "反应" 即倒置、控制反转的意思。具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示本身对某些事件感兴趣,有事件来了,具体事件处理程序经过事件处理器对某个指定的事件发生作出反应;这种控制逆转又称为“好莱坞法则”(通俗点就是:你不要主动找我,有事我来找你)。
单线程Reactor模式
① 服务器端的Reactor是一个线程对象,该线程会启动事件循环,并使用Selector(选择器)来实现IO的多路复用。注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样Reactor会监听客户端向服务器端发起的链接请求事件(ACCEPT事件);
② 客户端向服务器端发起一个链接请求,Reactor监听到了该ACCEPT事件的发生并将该ACCEPT事件派发给相应的Acceptor处理器来进行处理。Acceptor处理器经过accept()方法获得与这个客户端对应的链接(SocketChannel),而后将该链接所关注的READ事件以及对应的READ事件处理器注册到Reactor中,这样一来Reactor就会监听该链接的READ事件了;
③ 当Reactor监听到有读或者写事件发生时,将相关的事件派发给对应的处理器进行处理。好比,读处理器会经过SocketChannel的read()方法读取数据,此时read()操做能够直接读取到数据,而不会堵塞与等待可读的数据到来;
④ 每当处理完全部就绪的感兴趣的I/O事件后,Reactor线程会再次执行select()阻塞等待新的事件就绪并将其分派给对应处理器进行处理;
注意,Reactor的单线程模式的单线程主要是针对于I/O操做而言,也就是全部的I/O的accept()、read()、write()以及connect()操做都在一个线程上完成的;
但在目前的单线程Reactor模式中,不只I/O操做在该Reactor线程上,连非I/O的业务操做也在该线程上进行处理了,这可能会大大延迟I/O请求的响应。因此咱们应该将非I/O的业务逻辑操做从Reactor线程上卸载,以此来加速Reactor线程对I/O请求的响应。
多线程Reactor模式
Reactor线程池中的每一Reactor线程都会有本身的Selector、线程和分发的事件循环逻辑。
mainReactor能够只有一个,但subReactor通常会有多个。mainReactor线程主要负责接收客户端的链接请求,而后将接收到的SocketChannel传递给subReactor,由subReactor来完成和客户端的通讯。交互流程以下:
① 注册一个Acceptor事件处理器到mainReactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样mainReactor会监听客户端向服务器端发起的链接请求事件(ACCEPT事件)。启动mainReactor的事件循环;
② 客户端向服务器端发起一个链接请求,mainReactor监听到了该ACCEPT事件并将该ACCEPT事件派发给Acceptor处理器来进行处理。Acceptor处理器经过accept()方法获得与这个客户端对应的链接(SocketChannel),而后将这个SocketChannel传递给subReactor线程池;
③ subReactor线程池分配一个subReactor线程给这个SocketChannel,即,将SocketChannel关注的READ事件以及对应的READ事件处理器注册到subReactor线程中。固然你也注册WRITE事件以及WRITE事件处理器到subReactor线程中以完成I/O写操做。Reactor线程池中的每一Reactor线程都会有本身的Selector、线程和分发的循环逻辑;
④ 当有I/O事件就绪时,相关的subReactor就将事件派发给响应的处理器处理。注意,这里subReactor线程只负责完成I/O的read()操做,在读取到数据后将业务逻辑的处理放入到线程池中完成,若完成业务逻辑后须要返回数据给客户端,则相关的I/O的write操做仍是会被提交回subReactor线程来完成;
注意,因此的I/O操做(包括,I/O的accept()、read()、write()以及connect()操做)依旧仍是在Reactor线程(mainReactor线程 或 subReactor线程)中完成的。Thread Pool(线程池)仅用来处理非I/O操做的逻辑;
多Reactor线程模式将“接受客户端的链接请求”和“与该客户端的通讯”分在了两个Reactor线程来完成。mainReactor完成接收客户端链接请求的操做,它不负责与客户端的通讯,而是将创建好的链接转交给subReactor线程来完成与客户端的通讯,这样一来就不会由于read()数据量太大而致使后面的客户端链接请求得不到即时处理的状况。而且多Reactor线程模式在海量的客户端并发请求的状况下,还能够经过实现subReactor线程池来将海量的链接分发给多个subReactor线程,在多核的操做系统中这能大大提高应用的负载和吞吐量,Netty服务端使用了多Reactor线程模式。
第二节、Netty和选择Netty的理由
Netty是由jbss提供的一个java开源框架, 它提供异步的、事件驱动网络编程框架和工具;支撑开发快速、高性能、高稳定性的端到端(服务端—网络端)程序。咱们经常使用的Netty大版本是Netty4,而最新的Netty5严格地说,仍是alpha版本,还有一些问题且并没通过实战。
选择Netty的理由
1、虽然JAVA NIO框架提供了 多路复用IO的支持,可是并无提供上层“信息格式”的良好封装。例如前二者并无提供针对 Protocol Buffer、JSON这些信息格式的封装,可是Netty框架提供了这些数据格式封装(基于责任链模式的编码和解码功能);
2、NIO的类库和API至关复杂,使用它来开发,须要很是熟练地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等,须要不少额外的编程技能来辅助使用NIO,例如,由于NIO涉及了Reactor线程模型,因此必须必须对多线程和网络编程很是熟悉才能写出高质量的NIO程序;
3、要编写一个可靠的、易维护的、高性能的NIO服务器应用。除了框架自己要兼容实现各种操做系统的实现外。更重要的是它应该还要处理不少上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等,这些Netty框架都提供了响应的支持;
4、JAVA NIO框架存在一个poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味着CPU的使用率会变成100%(这是底层JNI的问题,上层要处理这个异常实际上也好办)。固然这个bug只有在Linux内核上才能重现;
这个问题在JDK 1.7版本中尚未被彻底解决,可是Netty已经将这个bug进行了处理。
这个Bug与操做系统机制有关系的,JDK虽然仅仅是一个兼容各个操做系统平台的软件,但在JDK5和JDK6最初的版本中(严格意义上来将,JDK部分版本都是),这个问题并无解决,而将这个帽子抛给了操做系统方,这也就是这个bug最终一直到2013年才最终修复的缘由(JDK7和JDK8之间)。
第三节、Netty入门中三个基本特性
事件和Channel
Netty 使用不一样的事件来通知咱们状态的改变或者是操做的状态。这使得咱们可以基于已经发生的事件来触发适当的动做。由入站数据或者相关的状态更改而触发的事件包括:链接被激活、非激活事件、数据读取、用户事件、异常事件等;出站事件是将来将会触发的某个动做的操做结果,包括:打开或关闭到远程节点的链接、将数据写到或者冲刷到套接字等;
事件被分发给ChannelHandler 类中每一个覆盖的方法中, 这些方法相似于回调函数。Netty 提供了大量开箱即用的ChannelHandler 实现,包括用于各类协议(如HTTP 和SSL/TLS)的ChannelHandler。
Channel 接口
基本的I/O 操做(bind()、connect()、read()和write())依赖于底层网络传输所提供的原语。在基于Java 的网络编程中,其基本的构造是类Socket。Netty 的Channel 接口所提供的API,被用于全部的I/O 操做。大大地下降了直接使用Socket 类的复杂性。此外,Channel 也是拥有许多预约义的、专门化实现的普遍类层次结构的根。
因为Channel 是独一无二的,因此为了保证顺序将Channel 声明为java.lang.Comparable 的一个子接口。所以,若是两个不一样的Channel 实例都返回了相同的散列码,那么AbstractChannel 中的compareTo()方法的实现将会抛出一个Error。
Channel 的生命周期状态
ChannelUnregistered :Channel 已经被建立,但还未注册到EventLoop
ChannelRegistered :Channel 已经被注册到了EventLoop
ChannelActive :Channel 处于活动状态(已经链接到它的远程节点)。它如今能够接收和发送数据了
ChannelInactive :Channel 没有链接到远程节点
当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给ChannelPipeline 中的ChannelHandler,其能够随后对它们作出响应。
第四节、核心概念和机制 - EventLoop、EventLoopGroup
在内部,当提交任务到若是(当前)调用线程正是支撑EventLoop 的线程,那么所提交的代码块将会被(直接)执行。不然,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。
服务于Channel 的I/O 和事件的EventLoop 则包含在EventLoopGroup 中。
异步传输实现只使用了少许的EventLoop(以及和它们相关联的Thread),并且在当前的线程模型中,它们可能会被多个Channel 所共享。这使得能够经过尽量少许的Thread 来支撑大量的Channel,而不是每一个Channel 分配一个Thread。EventLoopGroup 负责为每一个新建立的Channel 分配一个EventLoop。在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,而且相同的EventLoop可能会被分配给多个Channel。
一旦一个Channel 被分配给一个EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的Thread)。请牢记这一点,由于它可使你从担心你的ChannelHandler 实现中的线程安全和同步问题中解脱出来。
须要注意EventLoop 的分配方式对ThreadLocal 的使用的影响。由于一个EventLoop 一般会被用于支撑多个Channel,因此对于全部相关联的Channel 来讲,ThreadLocal 都将是同样的。这使得它对于实现状态追踪等功能来讲是个糟糕的选择。然而,在一些无状态的上下文中,它仍然能够被用于在多个Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件。
第五节、主要组件ChannelHandler、ChannelHandlerContext和ChnnelPipeline
ChannelHandler接口
Netty 的主要组件是ChannelHandler,它充当了全部处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。
Netty 定义了下面两个重要的ChannelHandler 子接口:ChannelInboundHandler——处理入站数据以及各类状态变化;ChannelOutboundHandler——处理出站数据而且容许拦截全部的操做。
ChannelInboundHandler接口的生命周期中重要的方法:
channelActive: 当Channel 处于活动状态时被调用;Channel 已经链接/绑定而且已经就绪
channelReadComplete: 当Channel上的一个读操做完成时被调用
channelRead: 当从Channel 读取数据时被调用
userEventTriggered: 当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用
ChannelOutboundHandler接口的生命周期中重要的方法:
bind:当请求将Channel 绑定到本地地址时被调用
connect:当请求将Channel 链接到远程节点时被调用
close:当请求关闭Channel 时被调用
read: 当请求从Channel 读取更多的数据时被调用
flush: 当请求经过Channel 将入队数据冲刷到远程节点时被调用
write:当请求经过Channel 将数据写到远程节点时被调用
ChannelHandlerContext经常使用API
bind: 绑定到给定的SocketAddress,并返回ChannelFuture
channel: 返回绑定到这个实例的Channel
close: 关闭Channel,并返回ChannelFuture
connect: 链接给定的SocketAddress,并返回ChannelFuture
fireChannelActive: 触发对下一个ChannelInboundHandler 上的channelActive()方法(已链接)的调用
fireChannelRead: 触发对下一个ChannelInboundHandler 上的channelRead()方法(已接收的消息)的调用
fireChannelReadComplete: 触发对下一个ChannelInboundHandler 上的channelReadComplete()方法的调用
fireExceptionCaught: 触发对下一个ChannelInboundHandler 上的fireExceptionCaught(Throwable)方法的调用
fireUserEventTriggered: 触发对下一个ChannelInboundHandler 上的fireUserEventTriggered(Object evt)方法的调用
handler: 返回绑定到这个实例的ChannelHandler
pipeline: 返回这个实例所关联的ChannelPipeline
read 将数据从Channel读取到第一个入站缓冲区;若是读取成功则触发一个channelRead事件,并(在最后一个消息被读取完成后)通知ChannelInboundHandler 的channelReadComplete
ChnnelPipeline接口
当Channel 被建立时,它将会被自动地分配一个新的ChannelPipeline。这项关联是永久性的;Channel 既不能附加另一个ChannelPipeline,也不能分离其当前的。在Netty 组件的生命周期中,这是一项固定的操做,不须要开发人员的任何干预。
当Channel 被建立时,它将会被自动地分配一个新的ChannelPipeline。这项关联是永久性的;Channel 既不能附加另一个ChannelPipeline,也不能分离其当前的。在Netty 组件的生命周期中,这是一项固定的操做,不须要开发人员的任何干预。
使得事件流经ChannelPipeline 是ChannelHandler 的工做,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。
入站和出站ChannelHandler 能够被安装到同一个ChannelPipeline中。若是一个消息或者任何其余的入站事件被读取,那么它会从ChannelPipeline 的头部开始流动,最终,数据将会到达ChannelPipeline 的尾端,届时,全部处理就都结束了。
数据的出站运动(即正在被写的数据)在概念上也是同样的。在这种状况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这以后,出站数据将会到达网络传输层,这里显示为Socket。一般状况下,这将触发一个写操做。
若是将两个类别的ChannelHandler都混合添加到同一个ChannelPipeline 中会发生什么。虽然ChannelInboundHandle 和ChannelOutboundHandle 都扩展自ChannelHandler,可是Netty 能区分ChannelInboundHandler实现和ChannelOutboundHandler 实现,并确保数据只会在具备相同定向类型的两个ChannelHandler 之间传递。
ChannelPipeline上的重要的方法:addFirst、addBefore、addAfter、addLast。
第六节、Netty支持的网络通信传输模式
NIO: 使用java.nio.channels 包做为基础——基于选择器的方式;
Epoll: 由 JNI 驱动的 epoll()和非阻塞 IO。这个传输支持只有在Linux 上可用的多种特性,如SO_REUSEPORT,比NIO 传输更快,并且是彻底非阻塞的。将NioEventLoopGroup替换为 EpollEventLoopGroup , 而且将NioServerSocketChannel.class 替换为EpollServerSocketChannel.class 便可;
OIO: 使用java.net 包做为基础——使用阻塞流;
Local:能够在VM 内部经过管道进行通讯的本地传输;
Embedded: 传输容许使用ChannelHandler 而又不须要一个真正的基于网络的传输。在测试ChannelHandler 实现时很是有用;
第七节、操做系统层ChannelOption详解
ChannelOption 的各类属性在套接字选项中都有对应。
ChannelOption.SO_BACKLOG
ChannelOption.SO_BACKLOG 对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,函数 listen(int socketfd,int backlog)用来初始化服务端可链接队列, 服务端处理客户端链接请求是顺序处理的,因此同一时间只能处理一个客户端链接,多 个客户端来的时候,服务端将不能处理的客户端链接请求放在队列中等待处理,backlog 参 数指定了队列的大小。
ChannelOption.SO_REUSEADDR
ChanneOption.SO_REUSEADDR 对应于套接字选项中的 SO_REUSEADDR,这个参数表示允 许重复使用本地地址和端口, 好比,某个服务器进程占用了 TCP 的 80 端口进行监听,此时再次监听该端口就会返回 错误,使用该参数就能够解决问题,该参数容许共用该端口,这个在服务器程序中比较常使 用,好比某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能容许其余进程 使用,并且程序死掉之后,内核一须要必定的时间才可以释放此端口,不设置 SO_REUSEADDR 就没法正常使用该端口。
ChannelOption.SO_KEEPALIVE
Channeloption.SO_KEEPALIVE 参数对应于套接字选项中的 SO_KEEPALIVE,该参数用于设 置 TCP 链接,当设置该选项之后,链接会测试连接的状态,这个选项用于可能长时间没有数 据交流的链接。当设置该选项之后,若是在两小时内没有数据的通讯时,TCP 会自动发送一 个活动探测数据报文。
ChannelOption.SO_SNDBUF 和 ChannelOption.SO_RCVBUF
ChannelOption.SO_SNDBUF 参数对应于套接字选项中的 SO_SNDBUF, ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF 这两个参数用于操做接 收缓冲区和发送缓冲区的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程 序读取成功,发送缓冲区用于保存发送数据,直到发送成功。
ChannelOption.SO_LINGER
ChannelOption.SO_LINGER 参数对应于套接字选项中的 SO_LINGER,Linux 内核默认的处理 方式是当用户调用 close()方法的时候,函数返回,在可能的状况下,尽可能发送数据,不 必定保证会发生剩余的数据,形成了数据的不肯定性,使用 SO_LINGER 能够阻塞 close()的调 用时间,直到数据彻底发送 。
ChannelOption.TCP_NODELAY
ChannelOption.TCP_NODELAY 参数对应于套接字选项中的 TCP_NODELAY,该参数的使用 与 Nagle 算法有关,Nagle 算法是将小的数据包组装为更大的帧而后进行发送,而不是输入 一次发送一次,所以在数据包不足的时候会等待其余数据的到了,组装成大的数据包进行发 送,虽然该方式有效提升网络的有效负载,可是却形成了延时,而该参数的做用就是禁止使 用 Nagle 算法,使用于小数据即时传输,于 TCP_NODELAY 相对应的是 TCP_CORK,该选项是 须要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。
第八节、特有的缓冲封装ByteBuf详解
ByteBuf是Netty在ByteBuffer基础上作的二次封装和扩展。它的API 具备如下优势:
1. 它能够被用户自定义的缓冲区类型扩展;
2. 经过内置的复合缓冲区类型实现了透明的零拷贝;
3. 容量能够按需增加(相似于 JDK 的 StringBuilder);
4. 在读和写这两种模式之间切换不须要调用 ByteBuffer 的 flip()方法;
5. 读和写使用了不一样的索引;
6. 支持方法的链式调用;
7. 支持引用计数; 支持池化;
ByteBuf 维护了两个不一样的索引,名称以 read 或者 write 开头的 ByteBuf 方法,将会推动其对应的索引,而名称以 set 或者 get 开头的操做则不会。
第九节、Netty中的重要机制引用计数
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,它要负责显式地释放 与池化的 ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法 ReferenceCountUtil.release() Netty 将使用 WARN 级别的日志消息记录未释放的资源,使得能够很是简单地在代码 中发现违规的实例。可是以这种方式管理资源可能很繁琐。一个更加简单的方式是使用 SimpleChannelInboundHandler,SimpleChannelInboundHandler 会自动释放资源。
一、对于入站请求,Netty 的 EventLoo 在处理 Channel 的读操做时进行分配 ByteBuf,对 于这类 ByteBuf,须要咱们自行进行释放,有三种方式,或者使用 SimpleChannelInboundHandler,或者在重写 channelRead()方法使用 ReferenceCountUtil.release()或者使用 ctx.fireChannelRead 继续向后传递;
二、对于出站请求,无论 ByteBuf 是否由咱们的业务建立的,当调用了 write 或者 writeAndFlush 方法后,Netty 会自动替咱们释放,不须要咱们业务代码自行释放。
第十节、Netty如何解决粘包/半包问题
粘包半包
假设客户端分别发送了两个数据包 D1 和 D2 给服务端,因为服务端一次读取到的字节 数是不肯定的,故可能存在如下 4 种状况。
1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包;
2. 服务端一次接收到了两个数据包,D1 和 D2 粘合在一块儿,被称为 TCP 粘包;
3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包和 D2 包的部分 内容,第二次读取到了 D2 包的剩余内容,这被称为 TCP 拆包;
4. 服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部份内容 D1_1,第 二次读取到了 D1 包的剩余内容 D1_2 和 D2 包的整包。
若是此时服务端 TCP 接收滑窗很是小,而数据包 D1 和 D2 比较大,颇有可能会发生第 五种可能,即服务端分屡次才能将 D1 和 D2 包接收彻底,期间发生屡次拆包;
粘包半包发生的缘由分析
因为 TCP 协议自己的机制(面向链接的可靠地协议-三次握手机制)客户端与服务器会 维持一个链接(Channel),数据在链接不断开的状况下,能够持续不断地将多个数据包发 往服务器,可是若是发送的网络数据包过小,那么他自己会启用 Nagle 算法(可配置是否启 用)对较小的数据包进行合并(基于此,TCP 的网络延迟要 UDP 的高些)而后再发送(超 时或者包大小足够)。
那么这样的话,服务器在接收到消息(数据流)的时候就没法区分哪 些数据包是客户端本身分开发送的,这样产生了粘包;服务器在接收到数据库后,放到缓冲 区中,若是消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个 数据包的状况,形成粘包现象。
粘包半包解决办法
因为底层的 TCP 没法理解上层的业务数据,因此在底层是没法保证数据包不被拆分和重组的,这个问题只能经过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案, 能够概括以下:
1. 在包尾增长分割符,好比回车换行符进行分割,例如 FTP 协议; 参见 cn.enjoyedu.nettybasic.splicing.linebase 和 cn.enjoyedu.nettybasic.splicing.delimiter 下的代码;
2. 消息定长,例如每一个报文的大小为固定长度 200 字节,若是不够,空位补空格; 参见 cn.enjoyedu.nettybasic.splicing.fixed 下的代码 ;
3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度) 的字段,一般设计思路为消息头的第一个字段使用 int32 来表示消息的总长度LengthFieldBasedFrameDecoder。
第十一节、Netty重量级组件编解码器
将字节解码为消息
抽象类 ByteToMessageDecoder
将字节解码为消息(或者另外一个字节序列)是一项如此常见的任务,以致于 Netty 为它 提供了一个抽象的基类:ByteToMessageDecoder。因为你不可能知道远程节点是否会一次性 地发送一个完整的消息,因此这个类会对入站数据进行缓冲,直到它准备好处理。
decode(ChannelHandlerContext ctx,ByteBuf in,List out)
这是你必须实现的惟一抽象方法。decode()方法被调用时将会传入一个包含了传入数据 的 ByteBuf,以及一个用来添加解码消息的 List。
将一种消息类型解码为另外一种
decode(ChannelHandlerContext ctx,I msg,List out)
对于每一个须要被解码为另外一种格式的入站消息来讲,该方法都将会被调用。解码消息随 后会被传递给 ChannelPipeline 中的下一个 ChannelInboundHandler
TooLongFrameException
因为 Netty 是一个异步框架,因此须要在字节能够解码以前在内存中缓冲它们。所以, 不能让解码器缓冲大量的数据以致于耗尽可用的内存。为了解除这个常见的顾虑,Netty 提 供了 TooLongFrameException 类,其将由解码器在帧超出指定的大小限制时抛出。
第十二节、Netty内置及引入外部序列和反序列化部件
序列化的问题
Java 序列化的目的主要有两个:
1.网络传输
2.对象持久化
当选行远程跨迸程服务调用时,须要把被传输的 Java 对象编码为字节数组或者 ByteBuffer 对象。而当远程服务读取到 ByteBuffer 对象或者字节数组时,须要将其解码为发 送时的 Java 对象。这被称为 Java 对象编解码技术。
Java 序列化仅仅是 Java 编解码技术的一种,因为它的种种缺陷,衍生出了多种编解码 技术和框架;
Java 序列化的缺点
1. 没法跨语言
对于跨进程的服务调用,服务提供者可能会使用 C 十+或者其余语言开发,当咱们须要 和异构语言进程交互时 Java 序列化就难以胜任。因为 Java 序列化技术是 Java 语言内部的私 有协议,其余语言并不支持,对于用户来讲它彻底是黑盒。对于 Java 序列化后的字节数组, 别的语言没法进行反序列化,这就严重阻碍了它的应用。
2. 序列化后的码流太大
经过不少验证代码证实:序列化后的码流确实很大;
3. 序列化性能过低
不管是序列化后的码流大小,仍是序列化的性能,JDK 默认的序列化机制表现得都不好。
所以,咱们边常不会选择 Java 序列化做为远程跨节点调用的编解码框架。
Netty内置的对象序列和反序列化组件是:Protocol Buffers
外部业界比较高效地序列和反序列化的部件有:
protostuff 、 kryo 、 fast-serialization 、msgpack-databird、 hessian 等,尤为是前三甲;
第十三节、如何独立进行ChannelHandler的单元测试
EmbeddedChannel---ChannelHandler独立单元测试工具
将入站数据或者出站数据写入到 EmbeddedChannel 中,而后检查是否有任何东西到达 了 ChannelPipeline 的尾端。以这种方式,你即可以肯定消息是否已经被编码或者被解码过 了,以及是否触发了任何的 ChannelHandler 动做。
writeInbound(Object... msgs)
将入站消息写到 EmbeddedChannel 中。若是能够经过 readInbound()方法从 EmbeddedChannel 中读取数据,则返回 true。
readInbound()
从 EmbeddedChannel 中读取一个入站消息。任何返回的东西都穿越了整个 ChannelPipeline。若是没有任何可供读取的,则返回 null。
writeOutbound(Object... msgs)
将出站消息写到 EmbeddedChannel 中。若是如今能够经过 readOutbound()方法从 EmbeddedChannel 中读取到什么东西,则返回 true。
readOutbound()
从 EmbeddedChannel 中读取一个出站消息。任何返回的东西都穿越了整个 ChannelPipeline。若是没有任何可供读取的,则返回 null。
finish()
将 EmbeddedChannel 标记为完成,而且若是有可被读取的入站数据或者出站数 据,则返回 true。这个方法还将会调用 EmbeddedChannel 上的 close()方法。
入站数据由 ChannelInboundHandler 处理,表明从远程节点读取的数据。出站数据由 ChannelOutboundHandler 处理,表明将要写到远程节点的数据。
使用 writeOutbound()方法将消息写到 Channel 中,并经过 ChannelPipeline 沿着出站的 方向传递。随后,你可使用 readOutbound()方法来读取已被处理过的消息,以肯定结果是 否和预期同样。 相似地,对于入站数据,你须要使用 writeInbound()和 readInbound()方法。
总结
咱们应该尽可能对以上Netty相关知识进行理解,尤为是EventLoop(Group)、ChannelHandler、ChannelHandlerContext、ChannelPipeline、ByteBuf、Netty编解码器、序列化和反序列化等作深刻理解。那么有了这些知识储备后,咱们就能够基于一些业务来设计实现一个Netty端到端的网络IO实战了。下次咱们将运用以上知识或机制,逐步深刻给你们展现Netty的实战相关课题。