第三章:Netty核心概念

这一章咱们将讨论Netty的10个核心类。编程

Bootstrapbootstrap

ServerBootstrap服务器

EventLoop网络

EventLoopGroup多线程

ChannelPipelineapp

Channel异步

Futuresocket

ChannelFutureoop

ChannelInitializer性能

ChannelHandler

1.Crash Course(速成课程)

一个Netty程序开始于Bootstrap类,Bootstrap类是Netty提供的一个能够经过简单配置来设置或"引导"程序的一个很重要的类。

Netty中设计了Handlers来处理特定的"event"和设置Netty中的事件,从而来处理多个协议和数据。

事件能够描述成一个很是通用的方法,由于你能够自定义一个handler,用来将Object转成byte[]或将byte[]转成Object;也能够定义个handler处理抛出的异常。

你会常常编写一个实现ChannelInboundHandler的类,ChannelInboundHandler是用来接收消息,当有消息过来时,你能够决定如何处理。当程序须要返回消息时能够在ChannelInboundHandler里write/flush数据。能够认为应用程序的业务逻辑都是在ChannelInboundHandler中来处理的,业务罗的生命周期在ChannelInboundHandler中。

Netty链接客户端或绑定服务器须要知道如何发送或接收消息,这是经过不一样类型的handlers来作的,多个Handlers是怎么配置的?Netty提供了ChannelInitializer类用来配置Handlers。

ChannelInitializer是经过ChannelPipeline来添加ChannelHandler的,如发送和接收消息,这些Handlers将肯定发的是什么消息。ChannelInitializer自身也是一个ChannelHandler,在添加完其余的handlers以后会自动从ChannelPipeline中删除本身。

全部的Netty程序都是基于ChannelPipeline。ChannelPipeline和EventLoop和EventLoopGroup密切相关,由于它们三个都和事件处理相关,因此这就是为何它们处理IO的工做由EventLoop管理的缘由。

Netty中全部的IO操做都是异步执行的,例如你链接一个主机默认是异步完成的;写入/发送消息也是一样是异步。也就是说操做不会直接执行,而是会等一会执行,由于你不知道返回的操做结果是成功仍是失败,可是须要有检查是否成功的方法或者是注册监听来通知,Netty使用Futures和ChannelFutures来达到这种目的

Future注册一个监听,当操做成功或失败时会通知。ChannelFuture封装的是一个操做的相关信息,操做被执行时会马上返回ChannelFuture。

2 Channels,Events and Input/Output(IO)

Netty是使用多线程处理IO,对于多线程编程读者可能须要同步,这样会影响程序性能,Netty的设置保证不会用到同步。

下图显示一个EventLoopGroup和一个Channel关联一个单一的EventLoop,Netty中的EventLoopGroup包含一个或多个EventLoop,而EventLoop就是一个Channel执行实际工做的线程。EventLoop老是绑定一个单一的线程,在其生命周期内不会改变。

当注册一个Channel后,Netty将这个Channel绑定到一个EventLoop,在Channel的生命周期内老是被绑定到一个EventLoop。在Netty IO操做中,你的程序不须要同步,由于一个指定通道的全部IO始终由同一个线程来执行。

EventLoop和EventLoopGroup的关联不是直观的,由于咱们说过EventLoopGroup包含一个或多个EventLoop,可是上面的图显示EventLoop是一个EventLoopGroup,这意味着你能够只使用一个特定的EventLoop。

3 什么是Bootstrap?为何使用它?

引导”是Netty中配置程序的过程,当你须要链接客户端或服务器绑定指定端口时须要使用bootstrap。

引导”有两种类型,一种是用于客户端的Bootstrap(也适用于DatagramChannel),一种是用于服务端的ServerBootstrap。

Bootstrap和ServerBootstrap之间的差别:

1.Bootstrap用来链接远程主机,有1个EventLoopGroup

2.ServerBootstrap用来绑定本地端口,有2个EventLoopGroup

第一个差别很明显,“ServerBootstrap”监听在服务器,监听一个端口轮询客户端的“Bootstrap”或DatagramChannel是否链接服务器。一般须要调用“Bootstrap”类的connect()方法,可是也能够先调用bind()再调用connect()进行链接,以后使用的Channel包含在bind()返回的ChannelFuture中。

第二个差异也许是最重要的。客户端bootstraps/applications使用一个单例EventLoopGroup,而ServerBootstrap使用2个EventLoopGroup(实际上使用的是相同的实例),它可能不是显而易见的,可是它是个好的方案。一个ServerBootstrap能够认为有2个channels组,第一组包含一个单例ServerChannel,表明持有一个绑定了本地端口的socket;第二组包含全部的Channel,表明服务器已接受了的链接。下图形象的描述了这种状况:

EventLoopGroup A惟一的目的就是接受链接而后交给EventLoopGroup B。

Netty可使用两个不一样的Group,由于服务器程序须要接受不少客户端链接的状况下,一个EventLoopGroup将是程序性能的瓶颈,由于事件循环忙于处理链接请求,没有多余的资源和空闲来处理业务逻辑,最后的结果会是不少链接请求超时。如有两EventLoops, 即便在高负载下,全部的链接也都会被接受,由于EventLoops接受链接不会和哪些已经链接了的处理共享资源。

EventLoopGroup和EventLoop是什么关系?

EventLoopGroup能够包含不少个EventLoop,每一个Channel绑定一个EventLoop不会被改变,由于EventLoopGroup包含少许的EventLoop的Channels,不少Channel会共享同一个EventLoop。这意味着在一个Channel保持EventLoop繁忙会禁止其余Channel绑定到相同的EventLoop。咱们能够理解为EventLoop是一个事件循环线程,而EventLoopGroup是一个事件循环集合。

 若是你决定两次使用相同的EventLoopGroup实例配置Netty服务器,下图显示了它是如何改变的:

Netty容许处理IO和接受链接使用同一个EventLoopGroup,这在实际中适用于多种应用。上图显示了一个EventLoopGroup处理链接请求和IO操做。

4 Channel Handlers and Data Flow(通道处理和数据流)

本节咱们一块儿来看看当你发送或接收数据时发生了什么?要明白Netty程序wirte或read时发生了什么,首先要对Handler是什么有必定的了解。

Handlers自身依赖于ChannelPipeline来决定它们执行的顺序,所以不可能经过ChannelPipeline定义处理程序的某些方面,反过来不可能定义也不可能经过ChannelHandler定义ChannelPipeline的某些方面。不必说咱们必须定义一个本身和其余的规定。本节将介绍ChannelHandler和ChannelPipeline在某种程度上细微的依赖。

Netty的ChannelHandler是你的应用程序中处理最多的。ChannelHandler到底是什么?ChannelHandler是一段执行业务逻辑处理数据的代码,它们来来每每的经过ChannelPipeline。ChannelHandler是定义一个handler的父接口,ChannelInboundHandler和ChannelOutboundHandler都实现ChannelHandler接口

Netty中有两个方向的数据流,上图显示的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之间有一个明显的区别:若数据是从用户应用程序到远程主机则是“出站(outbound)”,相反若数据时从远程主机到用户应用程序则是“入站(inbound)”。

为了使数据从一端到达另外一端,一个或多个ChannelHandler将以某种方式操做数据。这些ChannelHandler会在程序的“引导”阶段被添加ChannelPipeline中,而且被添加的顺序将决定处理数据的顺序。ChannelPipeline的做用咱们能够理解为用来管理ChannelHandler的一个容器,每一个ChannelHandler处理各自的数据(例如入站数据只能由ChannelInboundHandler处理),处理完成后将转换的数据放到ChannelPipeline中交给下一个ChannelHandler继续处理,直到最后一个ChannelHandler处理完成。

下图显示了ChannelPipeline的处理过程:

上图显示ChannelInboundHandler和ChannelOutboundHandler都要通过相同的ChannelPipeline。

在ChannelPipeline中,若是消息被读取或有任何其余的入站事件,消息将从ChannelPipeline的头部开始传递给第一个ChannelInboundHandler,这个ChannelInboundHandler能够处理该消息或将消息传递到下一个ChannelInboundHandler中,一旦在ChannelPipeline中没有剩余的ChannelInboundHandler后,ChannelPipeline就知道消息已被全部的饿Handler处理完成了。

任何出站事件或写入将从ChannelPipeline的尾部开始,并传递到最后一个ChannelOutboundHandler。ChannelOutboundHandler的做用和ChannelInboundHandler相同,它能够传递事件消息到下一个Handler或者本身处理消息。不一样的是ChannelOutboundHandler是从ChannelPipeline的尾部开始,而ChannelInboundHandler是从ChannelPipeline的头部开始,当处理完第一个ChannelOutboundHandler处理完成后会出发一些操做,好比一个写操做。

一个事件能传递到下一个ChannelInboundHandler或上一个ChannelOutboundHandler,在ChannelPipeline中经过使用ChannelHandlerContext调用每个方法。

Netty提供了抽象的事件基类称为ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。

每一个都提供了在ChannelPipeline中经过调用相应的方法将事件传递给下一个Handler的方法的实现。咱们能覆盖的方法就是咱们须要作的处理。

出站和入站的操做不一样,能放在同一个ChannelPipeline工做?

入站和出站Handler有不一样的实现,Netty能跳过一个不能处理的操做,因此在出站事件的状况下,ChannelInboundHandler将被跳过,Netty知道每一个handler都必须实现ChannelInboundHandler或ChannelOutboundHandler。

当一个ChannelHandler添加到ChannelPipeline中时得到一个ChannelHandlerContext。

Netty中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext对象。这两种方法的主要区别以下:

    a. 直接写入通道致使处理消息从ChannelPipeline的尾部开始

    b. 写入ChannelHandlerContext对象致使处理消息从ChannelPipeline的下一个handler开始

5 编码器、解码器和业务逻辑:细看Handlers

Netty中有不少不一样类型的handlers,每一个handler的依赖于它们的基类。Netty提供了一系列的“Adapter”类,每一个handler负责转发事件到ChannelPipeline的下一个handler。

在*Adapter类(和子类)中是自动完成的,所以咱们只须要在感兴趣的*Adapter中重写方法。这些功能能够帮助咱们很是简单的编码/解码消息。

有几个适配器(adapter)容许自定义ChannelHandler,通常自定义ChannelHandler须要继承编码/解码适配器类中的一个。Netty有如下适配器:

ChannelHandlerAdapter

ChannelInboundHandlerAdapter

ChannelOutboundHandlerAdapter

三个ChannelHandler中,咱们重点看看encoders,decoders和SimpleChannelInboundHandler<I>,SimpleChannelInboundHandler<I>继承ChannelInboundHandlerAdapter。

5.1 Encoders(编码器), decoders(解码器)

发送或接收消息后,Netty必须将消息数据从一种形式转化为另外一种。接收消息后,须要将消息从字节码转成Java对象(由某种解码器解码);发送消息前,须要将Java对象转成字节(由某些类型的编码器进行编码)。这种转换通常发生在网络程序中,由于网络上只能传输字节数据。

有多种基础类型的编码器和解码器,要使用哪一种取决于想实现的功能。要弄清楚某种类型的编解码器,从类名就能够看出如“ByteToMessageDecoder”、“MessageToByteEncoder”,还有Google的协议“ProtobufEncoder”和“ProtobufDecoder”。

若是是解码器则有一个ChannelInboundHandlerAdapter或ChannelInboundHandler,全部的解码器都继承或实现它们。“channelRead”方法/事件被覆盖,这个方法从入站(inbound)通道读取每一个消息。重写的channelRead方法将调用每一个解码器的“decode”方法并经过ChannelHandlerContext.fireChannelRead(Object msg)传递给ChannelPipeline中的下一个ChannelInboundHandler。

相似入站消息,当你发送一个消息出去(出站)时,除编码器将消息转成字节码外还会转发到下一个ChannelOutboundHandler。

5.2 业务逻辑(Domain logic)

也许最多见的是应用程序处理接收到消息后进行解码,而后供相关业务逻辑模块使用。因此应用程序只须要扩展SimpleChannelInboundHandler<I>,也就是咱们自定义一个继承SimpleChannelInboundHandler<I>的handler类,其中<I>是handler能够处理的消息类型。经过重写父类的方法能够得到一个ChannelHandlerContext的引用,它们接受一个ChannelHandlerContext的参数,你能够在class中当一个属性存储。

处理程序关注的主要方法是“channelRead0(ChannelHandlerContext ctx, I msg)”,每当Netty调用这个方法,对象“I”是消息,这里使用了Java的泛型设计,程序就能处理I。如何处理消息彻底取决于程序的须要。在处理消息时有一点须要注意的,在Netty中事件处理IO通常有不少线程,程序中尽可能不要阻塞IO线程,由于阻塞会下降程序的性能。

必须不阻塞IO线程意味着在ChannelHandler中使用阻塞操做会有问题。幸运的是Netty提供了解决方案,咱们能够在添加ChannelHandler到ChannelPipeline中时指定一个EventExecutorGroup,EventExecutorGroup会得到一个EventExecutor,EventExecutor将执行ChannelHandler的全部方法。EventExecutor将使用不一样的线程来执行和释放EventLoop。

相关文章
相关标签/搜索