在这一章我们将讨论Netty的10个核心类。清楚了解他们的结构对使用Netty非常实用。可能有一些不会再工作中用到。可是也有一些非经常常使用也非常核心,你会遇到。

  • Bootstrap or ServerBootstrap
  • EventLoop
  • EventLoopGroup
  • ChannelPipeline
  • Channel
  • Future or ChannelFuture
  • ChannelInitializer
  • ChannelHandler

       本节的目的就是介绍以上这些概念。帮助你了解它们的使用方法。

3.1 Netty Crash Course

        在我们開始之前,假设你了解Netty程序的一般结构和大致使用方法(client和server都有一个类似的结构)会更好。

        一个Netty程序開始于Bootstrap类,Bootstrap类是Netty提供的一个能够通过简单配置来设置或"引导"程序的一个非常重要的类。Netty中设计了Handlers来处理特定的"event"和设置Netty中的事件,从而来处理多个协议和数据。

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

        你会经常编写一个实现ChannelInboundHandler的类,ChannelInboundHandler是用来接收消息。当有消息过来时。你能够决定怎样处理。当程序须要返回消息时能够在ChannelInboundHandler里write/flush数据。

能够觉得应用程序的业务逻辑都是在ChannelInboundHandler中来处理的。业务罗的生命周期在ChannelInboundHandler中。

        Netty连接client端或绑定server须要知道怎样发送或接收消息,这是通过不同类型的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。

3.2 Channels,Events and Input/Output(IO)

        Netty是一个非堵塞、事件驱动的网络框架。Netty实际上是使用多线程处理IO事件,对于熟悉多线程编程的读者可能会须要同步代码。

这样的方式不好,由于同步会影响程序的性能,Netty的设计保证程序处理事件不会有同步。

        下图显示一个EventLoopGroup和一个Channel关联一个单一的EventLoop。Netty中的EventLoopGroup包括一个或多个EventLoop。而EventLoop就是一个Channel运行实际工作的线程。EventLoop总是绑定一个单一的线程,在其生命周期内不会改变。
当注冊一个Channel后。Netty将这个Channel绑定到一个EventLoop。在Channel的生命周期内总是被绑定到一个EventLoop。在Netty IO操作中。你的程序不须要同步,由于一个指定通道的全部IO始终由同一个线程来运行。

        为了帮助理解,下图显示了EventLoop和EventLoopGroup的关系:
EventLoop和EventLoopGroup的关联不是直观的。由于我们说过EventLoopGroup包括一个或多个EventLoop。可是上面的图显示EventLoop是一个EventLoopGroup。这意味着你能够仅仅使用一个特定的EventLoop。

3.3 什么是Bootstrap?为什么使用它?

        “引导”是Netty中配置程序的过程,当你须要连接client或server绑定指定port时须要使用bootstrap。如前面所述。“引导”有两种类型,一种是用于client的Bootstrap(也适用于DatagramChannel),一种是用于服务端的ServerBootstrap。不管程序使用哪种协议。不管是创建一个client还是server都须要使用“引导”。

        两种bootsstraps之间有一些类似之处,事实上他们有非常多类似之处,也有一些不同。Bootstrap和ServerBootstrap之间的差异:
  • Bootstrap用来连接远程主机,有1个EventLoopGroup
  • ServerBootstrap用来绑定本地port,有2个EventLoopGroup

        事件组(Groups)。传输(transports)和处理程序(handlers)分别在本章后面讲述,我们在这里仅仅讨论两种"引导"的差异(Bootstrap和ServerBootstrap)。第一个差异非常明显,“ServerBootstrap”监听在server监听一个port轮询client的“Bootstrap”或DatagramChannel是否连接server。通常须要调用“Bootstrap”类的connect()方法。可是也能够先调用bind()再调用connect()进行连接,之后使用的Channel包括在bind()返回的ChannelFuture中。

        第二个差别或许是最重要的。clientbootstraps/applications使用一个单例EventLoopGroup,而ServerBootstrap使用2个EventLoopGroup(实际上使用的是同样的实例),它可能不是显而易见的。可是它是个好的方案。

一个ServerBootstrap能够觉得有2个channels组。第一组包括一个单例ServerChannel,代表持有一个绑定了本地port的socket。第二组包括全部的Channel。代表server已接受了的连接。

下图形象的描写叙述了这样的情况:

上图中,EventLoopGroup A唯一的目的就是接受连接然后交给EventLoopGroup B。Netty能够使用两个不同的Group。由于server程序须要接受非常多client连接的情况下,一个EventLoopGroup将是程序性能的瓶颈,由于事件循环忙于处理连接请求,没有多余的资源和空暇来处理业务逻辑,最后的结果会是非常多连接请求超时。若有两EventLoops, 即使在高负载下。全部的连接也都会被接受,由于EventLoops接受连接不会和哪些已经连接了的处理共享资源。
         EventLoopGroup和EventLoop是什么关系?EventLoopGroup能够包括非常多个EventLoop,每一个Channel绑定一个EventLoop不会被改变,由于EventLoopGroup包括少量的EventLoop的Channels,非常多Channel会共享同一个EventLoop。这意味着在一个Channel保持EventLoop繁忙会禁止其它Channel绑定到同样的EventLoop。我们能够理解为EventLoop是一个事件循环线程,而EventLoopGroup是一个事件循环集合。
        假设你决定两次使用同样的EventLoopGroup实例配置Nettyserver。下图显示了它是怎样改变的:
Netty同意处理IO和接受连接使用同一个EventLoopGroup,这在实际中适用于多种应用。上图显示了一个EventLoopGroup处理连接请求和IO操作。
        下一节我们将介绍Netty是怎样运行IO操作以及在什么时候运行。

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

        本节我们一起来看看当你发送或接收数据时发生了什么?回忆本章開始提到的handler概念。要明确Netty程序wirte或read时发生了什么,首先要对Handler是什么有一定的了解。

Handlers自身依赖于ChannelPipeline来决定它们运行的顺序,因此不可能通过ChannelPipeline定义处理程序的某些方面,反过来不可能定义也不可能通过ChannelHandler定义ChannelPipeline的某些方面。

不是必需说我们必须定义一个自己和其它的规定。

本节将介绍ChannelHandler和ChannelPipeline在某种程度上细微的依赖。

        在非常多地方,Netty的ChannelHandler是你的应用程序中处理最多的。

即使你没有意思到这一点,若果你使用Netty应用将至少有一个ChannelHandler參与。换句话说,ChannelHandler对非常多事情是关键的。那么ChannelHandler到底是什么?给ChannelHandler一个定义不easy,我们能够理解为ChannelHandler是一段运行业务逻辑处理数据的代码,它们来来往往的通过ChannelPipeline。

实际上。ChannelHandler是定义一个handler的父接口,ChannelInboundHandler和ChannelOutboundHandler都实现ChannelHandler接口,例如以下图:

上图显示的比較easy,更重要的是ChannelHandler在数据流方面的应用,在这里讨论的样例仅仅是一个简单的样例。ChannelHandler被应用在很多方面,在本书中会慢慢学习。

        Netty中有两个方向的数据流,上图显示的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之间有一个明显的差别:若数据是从用户应用程序到远程主机则是“出站(outbound)”。相反若数据时从远程主机到用户应用程序则是“入站(inbound)”。
        为了使数据从一端到达还有一端,一个或多个ChannelHandler将以某种方式操作数据。这些ChannelHandler会在程序的“引导”阶段被加入ChannelPipeline中,而且被加入的顺序将决定处理数据的顺序。

ChannelPipeline的作用我们能够理解为用来管理ChannelHandler的一个容器。每一个ChannelHandler处理各自的数据(比如入站数据仅仅能由ChannelInboundHandler处理),处理完毕后将转换的数据放到ChannelPipeline中交给下一个ChannelHandler继续处理,直到最后一个ChannelHandler处理完毕。

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

上图显示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工作?Netty的设计是非常巧妙的。入站和出站Handler有不同的实现,Netty能跳过一个不能处理的操作,所以在出站事件的情况下,ChannelInboundHandler将被跳过,Netty知道每一个handler都必须实现ChannelInboundHandler或ChannelOutboundHandler。

        当一个ChannelHandler加入到ChannelPipeline中时获得一个ChannelHandlerContext。一般是安全的获得这个对象的引用,可是当一个数据报协议如UDP时这是不对的,这个对象能够在之后用来获取底层通道,由于要用它来read/write消息。因此通道会保留。

也就是说Netty中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext对象。

这两种方法的主要差别例如以下:

  • 直接写入通道导致处理消息从ChannelPipeline的尾部開始
  • 写入ChannelHandlerContext对象导致处理消息从ChannelPipeline的下一个handler開始

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

        如前面所说。有非常多不同类型的handlers,每一个handler的依赖于它们的基类。Netty提供了一系列的“Adapter”类。这让事情变的非常easy。每一个handler负责转发时间到ChannelPipeline的下一个handler。在*Adapter类(和子类)中是自己主动完毕的。因此我们仅仅须要在感兴趣的*Adapter中重写方法。这些功能能够帮助我们非常easy的编码/解码消息。

有几个适配器(adapter)同意自己定义ChannelHandler,一般自己定义ChannelHandler须要继承编码/解码适配器类中的一个。Netty有一下适配器:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter

三个ChannelHandler涨,我们重点看看ecoders,decoders和SimpleChannelInboundHandler<I>。SimpleChannelInboundHandler<I>继承ChannelInboundHandlerAdapter。

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

        发送或接收消息后,Netty必须将消息数据从一种形式转化为还有一种。接收消息后。须要将消息从字节码转成Java对象(由某种解码器解码);发送消息前,须要将Java对象转成字节(由某些类型的编码器进行编码)。

这样的转换一般发生在网络程序中,由于网络上仅仅能传输字节数据。

        有多种基础类型的编码器和解码器,要使用哪种取决于想实现的功能。要弄清楚某种类型的编解码器,从类名就能够看出,如“ByteToMessageDecoder”、“MessageToByteEncoder”,还有Google的协议“ProtobufEncoder”和“ProtobufDecoder”。
        严格的说其它handlers能够做编码器和适配器,使用不同的Adapter classes取决你想要做什么。假设是解码器则有一个ChannelInboundHandlerAdapter或ChannelInboundHandler。全部的解码器都继承或实现它们。“channelRead”方法/事件被覆盖,这种方法从入站(inbound)通道读取每一个消息。

重写的channelRead方法将调用每一个解码器的“decode”方法并通过ChannelHandlerContext.fireChannelRead(Object
msg)传递给ChannelPipeline中的下一个ChannelInboundHandler。

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

3.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。

Netty In Action中文版 - 第三章:Netty核心概念的更多相关文章

  1. Netty In Action中文版 - 第六章:ChannelHandler

    本章介绍 ChannelPipeline ChannelHandlerContext ChannelHandler Inbound vs outbound(入站和出站) 接受连接或创建他们仅仅是你的应 ...

  2. Netty In Action中文版 - 第五章:Buffers(缓冲)

    本章介绍 ByteBuf ByteBufHolder ByteBufAllocator 使用这些接口分配缓冲和运行操作 每当你须要数据传输时,它必须包括一个缓冲区.Java NIO API自带的缓冲区 ...

  3. Netty In Action中文版 - 第四章:Transports(传输)

    本章内容 Transports(传输) NIO(non-blocking IO,New IO), OIO(Old IO,blocking IO), Local(本地), Embedded(嵌入式) U ...

  4. Netty In Action中文版 - 第七章:编解码器Codec

    http://blog.csdn.net/abc_key/article/details/38041143 本章介绍 Codec,编解码器 Decoder,解码器 Encoder,编码器 Netty提 ...

  5. Java Persistence with MyBatis 3(中文版) 第三章 使用XML配置SQL映射器

    关系型数据库和SQL是经受时间考验和验证的数据存储机制.和其他的ORM 框架如Hibernate不同,MyBatis鼓励开发者可以直接使用数据库,而不是将其对开发者隐藏,因为这样可以充分发挥数据库服务 ...

  6. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  7. Netty In Action中文版 - 第一章:Netty介绍

    本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 堵塞IO(blocking IO)和非堵塞IO(non-blocking IO)对照 Java NIO的问题和在Nett ...

  8. Netty In Action中文版 - 第十五章:选择正确的线程模型

    http://blog.csdn.net/abc_key/article/details/38419469 本章介绍 线程模型(thread-model) 事件循环(EventLoop) 并发(Con ...

  9. Netty In Action中国版 - 第二章:第一Netty程序

    本章介绍 获得Netty4最新的版本号 设置执行环境,以构建和执行netty程序 创建一个基于Netty的server和client 拦截和处理异常 编制和执行Nettyserver和client 本 ...

随机推荐

  1. 嵌入式ROOTFS transplantation

    作一个嵌入式Linux rootfs,并且实现 web 服务 1. 文件系统简介 •理论上说一个嵌入式设备如果内核能够运行起来,且不需要运行用户进程的话,是不需要文件系统的,文件系统简单的说就是一种目 ...

  2. codeforcess水题100道

    之所以在codeforces上找这100道水题的原因是为了巩固我对最近学的编程语言的掌握程度. 找的方式在codeforces上的PROBLEMSET中过的题最多的那些题里面出现的最前面的10个题型, ...

  3. linux个性化定制登录信息

    1./etc/motd /etc/motd即messageoftoday(布告栏信息),每次用户登录时,/etc/motd文件的内容会显示在用户的终端.系统管理员可以在文件中编辑系统活动消息,例如:管 ...

  4. echarts - 特殊需求实现方案汇总

    五分钟上手echarts echarts中 设置x||y轴文案.提示文字等为固定字数,超出显示"..." 关于echarts下钻功能的一些总结.js echarts - 特殊需求实 ...

  5. Egret中的三种单例写法

    1 普通的单例写法 as3中也是这么个写法. 缺点:每个单例类里都要写instance和getInstance. class Single{ private static instance:Singl ...

  6. iOS 通过(lame)将录制音频转换成Mp3

    版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明出处,保留原帖地址及作者署名. Url:http://blog.csdn.net/ysy441088327/article/detail ...

  7. Java秒杀简单设计二:数据库表和Dao层设计

    Java秒杀简单设计二:数据库表Dao层设计 上一篇中搭建springboot项目环境和设计数据库表  https://www.cnblogs.com/taiguyiba/p/9791431.html ...

  8. Spring Cloud Eureka 配置

    实例名配置       在Netflix Eureka的原生实现中,实例名采用主机名作为默认值,这样的设置使得在同一主机上无法启动多个相同的实例,所以在Spring Cloud Eureka的配置中, ...

  9. thinkCMF----使用自定义函数

    thinkCMF使用自定义函数:app 下新建 common.php

  10. CentOS7.5安装Tomcat8

    一.tomcat的简介 这是Apache Tomcat Servlet / JSP容器的文档包的顶级入口点 .的Apache Tomcat 8.0版实现了Servlet 3.1和JavaServer ...