读完这一章,我们基本上能够了解到Netty全部重要的组件,对Netty有一个全面的认识。这对下一步深入学习Netty是十分重要的,而学完这一章。我们事实上已经能够用Netty解决一些常规的问题了。
一、先纵览一下Netty。看看Netty都有哪些组件?
为了更好的理解和进一步深入Netty。我们先整体认识一下Netty用到的组件及它们在整个Netty架构中是怎么协调工作的。Netty应用中不可缺少的组件:
- Bootstrap or ServerBootstrap
- EventLoop
- EventLoopGroup
- ChannelPipeline
- Channel
- Future or ChannelFuture
- ChannelInitializer
- ChannelHandler
Bootstrap,一个Netty应用通常由一个Bootstrap開始,它主要作用是配置整个Netty程序。串联起各个组件。
Handler,为了支持各种协议和处理数据的方式。便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件非常广泛,比方能够是连接、数据接收、异常、数据转换等。
ChannelInboundHandler,一个最经常使用的Handler。这个Handler的作用就是处理接收到数据时的事件。也就是说,我们的业务逻辑一般就是写在这个Handler里面的,ChannelInboundHandler就是用来处理我们的核心业务逻辑。
ChannelInitializer,当一个链接建立时,我们须要知道怎么来接收或者发送数据。当然,我们有各种各样的Handler实现来处理它。那么ChannelInitializer便是用来配置这些Handler。它会提供一个ChannelPipeline,并把Handler增加到ChannelPipeline。
ChannelPipeline,一个Netty应用基于ChannelPipeline机制,这样的机制须要依赖于EventLoop和EventLoopGroup,由于它们三个都和事件或者事件处理相关。
EventLoops的目的是为Channel处理IO操作,一个EventLoop能够为多个Channel服务。
EventLoopGroup会包括多个EventLoop。
Channel代表了一个Socket链接,或者其他和IO操作相关的组件,它和EventLoop一起用来參与IO处理。
Future。在Netty中全部的IO操作都是异步的,因此。你不能立马得知消息是否被正确处理,可是我们能够过一会等它运行完毕或者直接注冊一个监听,详细的实现就是通过Future和ChannelFutures,他们能够注冊一个监听。当操作运行成功或失败时监听会自己主动触发。总之,全部的操作都会返回一个ChannelFuture。
二、Netty是怎样处理连接请求和业务逻辑的呢?-- Channels、Events 和 IO
Netty是一个非堵塞的、事件驱动的、网络编程框架。当然,我们非常easy理解Netty会用线程来处理IO事件,对于熟悉多线程编程的人来说,你也许会想到怎样同步你的代码,可是Netty不须要我们考虑这些。详细是这样:
一个Channel会相应一个EventLoop,而一个EventLoop会相应着一个线程。也就是说,仅有一个线程在负责一个Channel的IO操作。
关于这些名词之间的关系。能够见下图:
如图所看到的:当一个连接到达,Netty会注冊一个channel,然后EventLoopGroup会分配一个EventLoop绑定到这个channel,在这个channel的整个生命周期过程中,都会由绑定的这个EventLoop来为它服务,而这个EventLoop就是一个线程。
讲到这里,那么EventLoops和EventLoopGroups关系是怎样的呢?我们前面说过一个EventLoopGroup包括多个Eventloop,可是我们看一下以下这幅图,这幅图是一个继承树,从这幅图中我们能够看出,EventLoop事实上继承自EventloopGroup,也就是说,在某些情况下,我们能够把一个EventLoopGroup当做一个EventLoop来用。
三、我们来看看怎样配置一个Netty应用?-- BootsStrapping
我们利用BootsStrapping来配置netty 应用。它有两种类型,一种用于Client端:BootsStrap。还有一种用于Server端:ServerBootstrap。要想差别怎样使用它们,你仅须要记住一个用在Client端。一个用在Server端。以下我们来具体介绍一下这两种类型的差别:
1.第一个最明显的差别是。ServerBootstrap用于Server端。通过调用bind()方法来绑定到一个port监听连接;Bootstrap用于Client端,须要调用connect()方法来连接server端,但我们也能够通过调用bind()方法返回的ChannelFuture中获取Channel去connectserver端。
2.client的Bootstrap一般用一个EventLoopGroup,而server端的ServerBootstrap会用到两个(这两个也能够是同一个实例)。为何server端要用到两个EventLoopGroup呢?这么设计有明显的优点,假设一个ServerBootstrap有两个EventLoopGroup,那么就能够把第一个EventLoopGroup用来专门负责绑定到port监听连接事件。而把第二个EventLoopGroup用来处理每一个接收到的连接,以下我们用一幅图来展现一下这样的模式:
PS: 假设仅由一个EventLoopGroup处理全部请求和连接的话。在并发量非常大的情况下,这个EventLoopGroup有可能会忙于处理已经接收到的连接而不能及时处理新的连接请求。用两个的话,会有专门的线程来处理连接请求,不会导致请求超时的情况。大大提高了并发处理能力。
我们知道一个Channel须要由一个EventLoop来绑定。并且两者一旦绑定就不会再改变。普通情况下一个EventLoopGroup中的EventLoop数量会少于Channel数量。那么就非常有可能出现一个多个Channel公用一个EventLoop的情况,这就意味着假设一个Channel中的EventLoop非常忙的话,会影响到这个Eventloop对其他Channel的处理,这也就是为什么我们不能堵塞EventLoop的原因。
当然,我们的Server也能够仅仅用一个EventLoopGroup,由一个实例来处理连接请求和IO事件。请看以下这幅图:
四、我们看看Netty是怎样处理数据的?-- Netty核心ChannelHandler
以下我们来看一下netty中是如何处理数据的。回忆一下我们前面讲到的Handler,对了,就是它。说到Handler我们就不得不提ChannelPipeline。ChannelPipeline负责安排Handler的顺序及其运行,以下我们就来具体介绍一下他们:
ChannelPipeline and handlers
我们的应用程序中用到的最多的应该就是ChannelHandler。我们能够这么想象。数据在一个ChannelPipeline中流动,而ChannelHandler便是当中的一个个的小阀门。这些数据都会经过每个ChannelHandler而且被它处理。这里有一个公共接口ChannelHandler:
从上图中我们能够看到,ChannelHandler有两个子类ChannelInboundHandler和ChannelOutboundHandler,这两个类相应了两个数据流向。假设数据是从外部流入我们的应用程序,我们就看做是inbound,相反便是outbound。事实上ChannelHandler和Servlet有些类似,一个ChannelHandler处理完接收到的数据会传给下一个Handler,或者什么不处理,直接传递给下一个。以下我们看一下ChannelPipeline是怎样安排ChannelHandler的:
从上图中我们能够看到,一个ChannelPipeline能够把两种Handler(ChannelInboundHandler和ChannelOutboundHandler)混合在一起。当一个数据流进入ChannelPipeline时,它会从ChannelPipeline头部開始传给第一个ChannelInboundHandler,当第一个处理完后再传给下一个。一直传递到管道的尾部。与之相相应的是。当数据被写出时。它会从管道的尾部開始,先经过管道尾部的“最后”一个ChannelOutboundHandler,当它处理完毕后会传递给前一个ChannelOutboundHandler。
数据在各个Handler之间传递。这须要调用方法中传递的ChanneHandlerContext来操作, 在netty的API中提供了两个基类分ChannelOutboundHandlerAdapter和ChannelOutboundHandlerAdapter,他们只实现了调用ChanneHandlerContext来把消息传递给下一个Handler,由于我们只关心处理数据。因此我们的程序中能够继承这两个基类来帮助我们做这些。而我们仅需实现处理数据的部分就可以。
我们知道InboundHandler和OutboundHandler在ChannelPipeline中是混合在一起的。那么它们怎样区分彼此呢?事实上非常easy。由于它们各自实现的是不同的接口。对于inbound event,Netty会自己主动跳过OutboundHandler,相反若是outbound event。ChannelInboundHandler会被忽略掉。
当一个ChannelHandler被增加到ChannelPipeline中时。它便会获得一个ChannelHandlerContext的引用。而ChannelHandlerContext能够用来读写Netty中的数据流。因此,如今能够有两种方式来发送数据,一种是把数据直接写入Channel,一种是把数据写入ChannelHandlerContext,它们的差别是写入Channel的话,数据流会从Channel的头開始传递,而假设写入ChannelHandlerContext的话。数据流会流入管道中的下一个Handler。
五、我们最关心的部分。怎样处理我们的业务逻辑? -- Encoders, Decoders and Domain Logic
Netty中会有非常多Handler,详细是哪种Handler还要看它们继承的是InboundAdapter还是OutboundAdapter。当然。Netty中还提供了一些列的Adapter来帮助我们简化开发,我们知道在Channelpipeline中每个Handler都负责把Event传递给下一个Handler。假设有了这些辅助Adapter,这些额外的工作都可自己主动完毕,我们仅仅需覆盖实现我们真正关心的部分就可以。此外,另一些Adapter会提供一些额外的功能,比方编码和解码。那么以下我们就来看一下当中的三种经常使用的ChannelHandler:
Encoders和Decoders
由于我们在网络传输时仅仅能传输字节流,因此,才发送数据之前,我们必须把我们的message型转换为bytes,与之相应,我们在接收数据后,必须把接收到的bytes再转换成message。我们把bytes to message这个过程称作Decode(解码成我们能够理解的),把message to bytes这个过程成为Encode。
Netty中提供了非常多现成的编码/解码器,我们一般从他们的名字中便可知道他们的用途。如ByteToMessageDecoder、MessageToByteEncoder,如专门用来处理Google Protobuf协议的ProtobufEncoder、 ProtobufDecoder。
我们前面说过,详细是哪种Handler就要看它们继承的是InboundAdapter还是OutboundAdapter,对于Decoders,非常easy便能够知道它是继承自ChannelInboundHandlerAdapter或 ChannelInboundHandler,由于解码的意思是把ChannelPipeline传入的bytes解码成我们能够理解的message(即Java
Object),而ChannelInboundHandler正是处理Inbound Event,而Inbound Event中传入的正是字节流。
Decoder会覆盖当中的“ChannelRead()”方法。在这种方法中来调用详细的decode方法解码传递过来的字节流,然后通过调用ChannelHandlerContext.fireChannelRead(decodedMessage)方法把编码好的Message传递给下一个Handler。与之类似。Encoder就不必多少了。
Domain Logic
事实上我们最最关心的事情就是怎样处理接收到的解码后的数据。我们真正的业务逻辑便是处理接收到的数据。Netty提供了一个最经常使用的基类SimpleChannelInboundHandler<T>。当中T就是这个Handler处理的数据的类型(上一个Handler已经替我们解码好了),消息到达这个Handler时,Netty会自己主动调用这个Handler中的channelRead0(ChannelHandlerContext,T)方法。T是传递过来的数据对象,在这种方法中我们便能够随意写我们的业务逻辑了。
Netty从某方面来说就是一套NIO框架,在Java NIO基础上做了封装。所以要想学好Netty我建议先理解好Java NIO,建议大家阅读一下我的另两篇文章:
转载请说明出处,原文链接:http://blog.csdn.net/suifeng3051/article/details/28861883