Netty in Action (七) 第三章节 Netty组件和设计

这个章节包含:

1)Netty的架构设计和技术点

2)Channel,EventLoop和ChannelFuture

3)ChannelHandler 和 ChannelPipeline

4)Bootstrap



在第一章节中,我们讲述了java在高性能的网络编程的发展历史和对网络方面的技术基础的积累。这给对Netty的核心组件和构建模块分析提供了一个非常好的氛围


在第二章节中,我们扩大了我们的讨论范围。我们构建了我们第一个基于Netty的应用,通过构建简单的server端和client让我们了解了怎样启动Netty,也让我们有了亲身的体验用于业务逻辑处理的最最重要的ChannelHandler的API是怎样使用的了,同一时候,你也验证了Netty的开发环境是否搭建成功了


在这本书的接下来的内容中。我们是通过两种视角去学习探索Netty的,一个是从java的的class文件的角度去学习,一个是将Netty当做一个框架去学习的,这两个角度是不同的。可是却是非常接近的。对于Netty来说,对于写出高效,可反复利用且易维护的代码,这两个角度度都是极其重要的


从一个更高的角度去审视。Netty攻克了两个不同领域的问题。这两个领域我们一般称之为技术领域和架构领域,首先在技术层面来说,Netty是构建于Java的NIO基于异步事件驱动的框架,可以在高负载的情况保证应用的性能和可扩展性。事实上在架构层面,Netty包括了非常多的设计模式来使得应用程序的逻辑处理与网络层的处理解耦,在简化代码的同一时候。保证了代码的最大的可測试性,模块化性,和最大的可反复利用性


由于Netty的种种技术优势和卓越的设计,我们将更加深层次的去探究Netty的一个个独特奇异的组件,而且去研究这些组件是怎样一起协作工作来构建这么好的用户体验的框架架构的,通过了解这些设计和技术初衷,我们将从Netty的使用中获取莫大的优点。牢记这个目的,我们将深层次的去介绍我们之前已经粗浅解说过的Netty的组件


3.1 Channel, EventLoop, and ChannelFuture


在接下来的几个小节中,我们将为你解说Channel。EventLoop,ChannelFuture这几个组件的细节的讨论,这几个组件能够视为Netty网络模块抽象的几个典范


1)Channel-------keyword:Sockets

2)EventLoop-------keyword:控制流。多线程。并发

3)ChannelFuture-------keyword:异步通知


3.1.1 Interface Channel


主要的I/O操作(bind(),connect(),read()和write())这些都依赖于网络传输的最原始的支持,对于基于java构建的网络模型,最基础的构造应该是Socket,Netty的Channel接口提供的API能够大大降低直接使用Socket的复杂性,可是Channel仅仅是一个原始接口,有非常多详细的实现类实现了Channel中之前定义好的方法。以下给出一些详细类的清单:

3.1.2 Interface EventLoop


EventLoop定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象,我们将在第七章Netty的多线程模型中具体介绍EventLoop,如今我们仅仅须要了解3.1图中向我们展示的Channel,EventLoop,Thread和EventLoopGroup之间的关系就能够了

这些关系包含:

1)一个EventLoopGroup包括多个EventLoop

2)一个EventLoop在一个生命周期中仅仅绑定一个线程

3)一个channel上的全部I/O操作将被EventLoop绑定的指定线程处理

4)一个channel在其生命周期里仅仅注冊到一个单独的EventLoop

5)一个EventLoop给能够被分配多个channel


注意到这样的设计,在一个channel中的I/O操作时被同一个线程处理运行的,无形中消除了同步的须要


3.1.3 Interface ChannelFuture


与我们之前说明的一样。全部的I/O操作在Netty中操作是异步的,由于操作结果并不会马上返回。我们须要通过一个方法在稍后的时间里确定结果。出于这个目的。Netty提供了ChannelFuture,它的addListener()方法注冊一个channelFutureListener来通知操作是否被完毕了(无论成功与否)


MORE ON CHANNELFUTURE我们能够将ChannelFuture当成一个操作在未来返回的结果的一个占位符。尽管在运行的时候,无法精确的预測它的结果,由于它返回的结果取决于一些未知的因素。可是有一点能够肯定。结果肯定会被运行到,无论是对是错,另一点能够保证。全部属于同一个Channel的操作能够保证依照它调用的顺序被运行


我们将在第七章深入讨论EventLoop和EventLoopGroup


3.2 ChannelHandler and ChannelPipeline


如今我们将具体的探讨一下管理数据流和运行应用程序业务逻辑的组件


3.2.1 Interface ChannelHandler

从一个应用程序开发人员的角度上看,最最重要的Netty组件就是ChannelHandler了,由于它作为一个服务容器用来处理关于输入输出数据的业务逻辑,这是由于ChannelHandler中的方法将被网络的一些事件所触发,其实,ChannelHandler致力于全部事件动作的处理,比如将数据从一端转化格式传输到还有一端或者处理异常操作

举例来说。ChannelInboundHandler是ChannelHandler的一个子类,我们常常去实现这个接口,这个接口能够去接收输入的数据然后依照你应用的业务逻辑去处理数据,你也能够从一个ChannelInboundHandler中刷新数据当你发送一个响应反馈信息到一个连接着的client,应用程序的业务部分一般存在一个或者多个ChannelInboundHandler


3.2.2 Interface ChannelPipeline


一个ChannelPipeline提供了ChannelHandler链的容器,定义了在这个链中的数据的输入输出的传播方法的API,当一个channel创建的时候,它会被自己主动安排到属于它的ChannelPipeline中去,ChannelHandler被装载到ChannelPipeline中遵循下面几个原则:

1)一个ChannelInitializer被注冊到ServerBootStraping上

2)当ChannelInitializer的initChannel方法被调用的时候,ChannelInitializer会装载自己定义的一些ChannelHandler到管道上

3)ChannelInitializer将它自己从ChannelPipeline上移除



让我们更加深层次的去探究一下ChannelPipeline和ChannelHandler的共生关系。去检測一下当数据输入输出的时候发生了事件流程


ChannelHandler被设计用来详细的去支持一些用户的使用。你能够将其想象成一些正常代码的容器用于处理在ChannelPipeline中流入流出的数据。在下图3.2展示了2个ChannelHandler的衍生类


在管道中要做的行为动作已经在ChannelHandler初始化的时候定义好了,然后装载到ChannelPipeline中。在应用的启动执行阶段,这些对象接收事件。执行他们已经实现的业务逻辑处理,处理结束之后将数据传送到链中的下一个handler,顺序与它们被加入到链中的顺序一致。为了这些有用的目的,将ChannelHandler安排的井井有条,我们就定义了ChannelPipeline




图3.3说明了在Netty应用中输入输出数据不同的差别,从client的角度来说。假设移动的方向是从client到server端事件就被定义成outbound。相反则被定义成inbound


图3.3也向我们展示了输入输出的处理能够被装载在同一个管道中。假设不论什么信息或者输入流被读取,它的过程是这种,从管道的头部開始然后通过第一个ChannelInboundHandler,这个处理可能会改变里面的信息或者数据,这全然取决你详细的操作。之后数据会陆续通过链中其它的handler,最后数据会达到数据的尾端。截止到如今,数据才被处理完成



输出数据的行为特征在理论上也是与输入一样的,输出数据流从尾部进入经过链式的ChannelOutboundHandler的处理达到头部,此时。输出数据到达了网络传输端,在这个图中特指的就是socket,典型的这样的输出数据流,就是触发写的时候会发生



TIPS:关于inbound和outbound处理的很多其它细节

每一个事件能够在当前链式处理器中指定它的下一个处理器通过ChannelHandlerContext,这个能够看做一个參数传递给每一个方法,由于你有时候想忽略那些你不敢兴趣的处理。Netty提供了一个抽象类ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,这两个类通过调用ChannelHandlerContext中对应的方法来非常方便的过滤到一些不感兴趣的事件处理。你能够自己重写扩展这些方法来让你的数据流仅仅经过你敢兴趣的处理器处理


鉴于输入输出操作时不一样的。那么你肯定会有疑问假设将这两种类型的处理放置在同一个ChannelPipeline时会不会发生些什么呢。虽然输入和输出的Handler都是继承自ChannelHandler,可是Netty还是区分了他们的实现,一个叫ChannelInboundHandler。还有一个叫ChannelOutboundHandler,这样做的优点就是保证数据在传输处理的过程中被同向的处理器处理


当一个ChannelHandler被增加到ChannelPipeline中的时候,它将被分配一个相应的ChannelHandlerContext,这个代表着ChannelHandler和ChannelPipeline之间的绑定关系,虽然ChannelHandlerContext这个对象能够直接获取究竟层的Channel可是大多数情况下它被用为写入输出数据


在Netty中。有两种方式去发送信息,第一种你能够直接将数据写入Channel,另外一种你将输入写入与channelHandler关联的ChannelHandlerContext中,第一种方法会使消息从ChannelPipeline的尾部開始。后一个方法会使信息从ChannelPipeline的下一个处理器開始处理


3.2.3 A closer look at ChannelHandlers


我们之前说过,ChannelHandler有非常多不同的类型。每个详细的ChannelHandler的功能都被它的父类或者超类定义好了,Netty也以适配类的形式提供了大量的默认的处理实现。这样做的意向是简化应用程序的处理逻辑的开发。你已经看过了,每个在管道中的ChannelHandler为传入下一个处理器的事件做好准备,这样的准备一般由适配类或者他们的子类去自己主动完毕,而你仅仅须要重写你须要特殊处理的方法




TIPS:适配器?

一些适配的类是用来降低我们自己编写一些比較苍白的自己定义的实现类,由于这些适配的类为全部的方法在相应的接口中提供了一些默认的实现


当创建你自己的自己定义的处理器的时候,一下的一些适配类是你常常调用的

1)ChannelHandlerAdapter

2)ChannelInboundHandlerAdapter

3)ChannelOutboundHandlerAdapter

4)ChannelDuplexHandlerAdapter


接下来。我们将会调查一下ChannelHandler的子对象:encoders。decoders和ChannelInboundHandlerAdapter的子类SimpleChannelInboundHandler<T>


3.2.4 Encoders and decoders


当你使用Netty来接收或者发送信息的时候。数据的转化是必定发生的。输入获取的数据须要解码,解码的意思就是字节转化成还有一个格式,通常是java对象,假设是数据输出,那么过程就是相反的,数据须要进行编码,编码就是将对象变成字节,转化的原因也是非常easy的。由于网络传输的数据一致是字节


各种各样的抽象类提供了解码和编码的功能,依据详细的情况而言,使用不同的解码编码。你的应用或许有时候须要仅仅用中间状态的格式。而不是直接立马将你的信息转化成字节,此时你依然须要编码。这能够从父类衍生出来,为了确定一个最为合适的,你能够自己定义一个简单的命名规范


一般而言,主要的类将有类似MessageToByteEncoder和MessageToByteEncoder这种编码和解码的工具类。对于一些详细的对象类。你能够发现类似ProtobufEncoder和ProtobufDecoder编码解码类,用来支持谷歌的protocol buffers


严格来说,其它的处理器也能够进行编码和解码的处理,可是由于有一些适配处理类来简化创建channel处理器的原因,全部的由netty提供的解码编码的适配类要么继承ChannelInboundHandler要么继承ChannelOutboundHandler


你会发现对于数据的输入,我们一般重写channelRead的方法,这种方法在数据从channel中读入的时候调用。此时它会调用解码类提供的decode方法,解码后给管道中的下一个handler,对于数据输出,过程则是相反的


3.2.5 Abstract class SimpleChannelInboundHandler


大多数情况下。你须要一个处理器去接收一个须要解码信息,然后将你的业务逻辑处理作用于这些数据之上,为了创建这个一个功能的channelHandler。你仅仅须要继承SimpleChannelInboundHandler<T>就能够了。这里的T值得是你想要将信息转化成的java对象类型,在这个处理中你须要重写一个或者多个方法而且获取一个ChannelHandlerContext的引用,这个应用是当做每一个方法的參数被开发人员获取使用的


这个处理器最最重要的方法是channelRead0这种方法,这种方法在当前的I/O线程不被堵塞的情况下全然取决于你的实现,具体的信息我们下次讨论


3.3 Bootstrapping


Netty的bootstrap类提供了应用程序网络层的配置的功能,一般包含两块功能,第一是绑定一个进程到给定的port,第二是连接还有一个在一个详细port正在执行的进程


一般而言,我们将第一种方式视为启动服务端,另外一种方式视为启动客户端,这样的定义是简单且方便的,可是这样有点掩盖了“server”和“client”这两个重要的因素,这两个术语代表了不同的网络行为。一个是监听连接一个是建立一个或者多个进程的连接


CONNECTION-ORIENTED PROTOCOLS 请你牢记于心。而且当你要说“connection”这个术语的时候请慎重,由于connection仅仅指的是基于连接的协议。比如TCP协议,这个保证了两个终端的信息传输顺序


因此,有两种类型的启动类。一个是用于client端的。一般被叫做Bootstrap,还有一个是用于server端的。被叫做serverBootstrap。不管你应用程序使用的协议是什么类型和也不管数据所表现的形式是什么。唯一事情bootstrap要做的事情就是确定此时它执行的是客户端还是服务器端。表3.1比較了这两种启动类的不同




两种bootstrap第一种不同是:ServerBootstrap绑定port,由于server端必须监听连接。而Bootstrap被用在client端的应用上来连接一个远程主机。第二个不同的是意义重大的,客户端的Bootstraping仅仅须要一个EcentLoopGroup,二ServerBootstrap却须要两个,即便这两个是相同的实例,那么为什么呢?由于server端须要两个区分开的Channels。第一个包括一个serverChannel用来代码server端的自己的监听socket,绑定一个本地的port。第二个用来全部来自client端连接的且server端接收的全部创建好的channel的处理。图3.4说明了这个模型,这就是为什么须要2个EventLoopGroup了

 

与ServerChannel相关的EventLoopGroup会分配一个EventLoop,这个EventLoop负责创建channel来处理新连接的连接请求,一旦连接建立成功。第二个EventLoopGroup会为每个新创建的channel分配一个对象的EventLoop




3.4 Summary


这个章节,我们从技术和架构两个角度讨论了一下Netty。我们回想且深入讨论了我们之前章节介绍的一些概念和Netty的组件,特别是ChannelHandler,ChannelPipeline。Bootstraping


我们也介绍了ChannelHandler的一些衍生类,介绍了解码编码,描写叙述了他们在网络字节传输是对数据进行转化的详细实现


接下来的非常多章节将会更加深入的介绍这些组件,眼下的介绍的概述将有利于你心中对netty架构蓝图的形成


接下来的一个章节,我们将展示Netty提供的一些网络传输服务。也会告诉你依据你的应用场景选择正确的传输服务,做到物尽其用



posted on 2017-08-19 15:56  wgwyanfs  阅读(143)  评论(0编辑  收藏  举报

导航