bootstrap: 引导类,提供一个用于应用程序网络层配置的容器;
channel:底层网络传输API必须提供给I/O操作的接口;
channelHandler:channelHandler支持很多协议,并且提供用于数据处理的容器;
channelInboundHandler:这个类型接收到入站事件(包括接收到的数据)可以处理应用程序逻辑
channelPipeline:提供一个容器给channelHandler链并提供一个API用于管理沿着链入站和出站事件的流动。每个Channel都有自己的ChannelPipeline,是当Channel创建时自动被创建的。
EventLoop用于处理I/O操作。一个单一的EventLoop通常会处理多个Channel事件。一个EventLoopGroup可以含有多于一个的EventLoop和提供一个迭代用于检索清单中的下一个。
channelFuture:Netty所有的I/O操作都是异步的。因为一个操作可能无法立即返回,我们需要有一种方法在以后确定这个操作的返回结果。出于这个目的,Netty提供了接口ChannelFuture,它的addLiListener方法注册了一个ChannelFutureListener,当操作完成时,可以被通知(不管是否成功)。
Netty是一个异步事件驱动的NIO框架,Netty所有的IO操作都是异步非阻塞的。Netty实际上是使用Threads(多线程)处理I/O事件。熟悉多线程编程的读者可能需要关注同步代码,这样做不好,同步会影响程序的性能,Netty的设计保证程序处理事件不会同步。下图展示了Channel之间共享ChannelHandler实例的原因。
上图显示了,一个EventLoopGroup具有一个或多个EventLoop。可以这么理解EventLoop作为一个Thread给Channel执行工作。(实际上一个EventLoop是势必为它的生命周期一个线程)。
当创建一个Channel,Netty通过一个单独的EventLoop实例来注册这个Channel(并同样是一个Thread)的通道的使用寿命。这就是为什么应用程序不需要同步Netty的I/O操作;所有的Channel的I/O始终是用相同的线程来执行。
Bootstrapping(引导)是出现在Netty配置程序的过程中,BootStrapping在给程序绑定指定端口或者要连接客户端的时候使用到。
Bootstrapping有两种类型:
一种是用于客户端的Bootstrap
一种是用于服务端的ServerBootstrap
不管程序使用的是哪种协议创建一个客户端或者一个服务端,“引导”都是必须使用到的。
两种Bootstrapping之间有一些相似之处,也有一些不同。Bootstrap与ServerBootstrap之间的差异如下:
| 分类 | Bootstrap | ServerBootstrap |
| ------------------------------| ------------------------------| --------------------------- |
| 网络功能 | 连接远程主机和端口 | 绑定本地端口 |
| EventLoopGroup数量 | 1 | 2 |
Bootstrap用于连接远程主机和端口,有1个EventLoopGroup;
ServerBootstrap用于绑定本地端口,有2个EventLoopGroup;
ServerBootstrap在服务器监听一个端口轮训客户端的Bootstrap是否连接服务器。通常需要调用“Bootstrap”类的connect()方法,也可以先调用bind()方法再调用connect()方法进行连接,之后使用的Channel包含在bind()返回的ChannelFuture()中。
一个ServerBootstrap可以认为有2个channel集合,第一个集合包含一个单例ServerChannel,代表持有一个绑定本地端口的socket;第二个集合包含所有创建的Channel,处理服务器所接收到的客户端进来连接。下图形象的描述了这种情况:
与ServerChannel相关的EventLoopGroup分配一个EventLoop是负责创建Channels用于传入的连接请求。一旦请求接收,第二个EventLoopGroup分配一个EventLoop给她的Channel。
ChannelPipeline是ChannelHandler链的容器。
在我们的应用程序中ChannelHandler是许多方面的核心,ChannelHandler的用途广泛,在使用它的时候最好是当成一个普通的容器,通过ChannelPipeline处理进来的事件(包括数据)。
下图展示了ChannelInboundHandler和ChannelOutboundHandler继承自父类接口CHannelHandler。
Netty有两个方向的数据流,下图展示了数据的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之间的有一个明显的区别:若数据是从用户应用程序到远程主机则是“出站”,相反若数据是从远程主机到用户应用程序则是“入站”。
为了使数据从一端到另一端,一个或多个ChannelHandler讲义某种方式操作数据。这些ChannelHandler会在程序的“引导”阶段被添加到ChannelPipeline中,并且被添加的顺序将决定处理数据的顺序。
上图同样展示了进站和出站的处理器都可以被安装到相同的pipeline。本例子中,如果消息或其他入站事件被读到,将从pipeline头部开始,传递到第一个ChannelInboundHandler。该处理器可能会或者可能不会修改数据取决于其特定的功能,在这之后数据背后传递到下一个ChannelInboundHandler。最后,数据将到达pipeline的尾部,所有处理结束。
数据的出站运动(即数据被“写入”)在概念上是相同的。这种情况下的数据从尾部流过ChannelOutboundHandler的链,直达它的头部。超过这个点,出站数据将到达的网络传输,在这里显示为一个socket。通常,这将触发一个写入操作。
*更多Inbound、Outbound Handler*
*在当前的链(chain)中,事件可以通过ChannelHandlerContext传递给下一个handler。Netty为此提供了抽象基类ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,用来处理你想要处理的事件,这些提供方法的实现,可以简单的通过调用ChannelHandlerContext上相应的方法将事件传递给下一个handler。在实际的应用中,可以按需要覆盖相应的方法即可。*
Netty的ChannelInboundHandler的实现和ChannelOutboundHandler的实现是有区别的,从而保证数据的传递只从一个处理器到下一个处理器,保证正确的类型。
当channelHandler被添加到ChannelPipeline,它将得到一个ChannelHandlerContext,它代表一个ChannelHandler和ChannelPipeline之间的“绑定”。它通常是安全的保证对此对象的引用,除了当协议中使用的不是面向对象的连接(如:UDP)。而该对象可以被用来获得底层的Channel,他主要用来写出站数据。
实际上,在Netty发送消息可以采用两种方式:直接写消息给Channel或者写入ChannelHandlerContext对象。这两者主要的区别是,前一种方法会导致消息从ChannelPipeline的尾部开始,而后者导致消息从ChannelPipeline下一个处理器开始。
为了让开发逻辑变得简单,Netty提供一些默认的处理程序来实现形式的“adapter(适配器)”类。pipeline每个ChannelHandler转发时间到链的下一个处理器,这些适配器类(及其子类)会自动帮你实现,所以只需要实现特定的方法和事件
*为什么用适配器
有几个适配器类,可以减少编写自定义ChannelHandler,因为它们提供对应接口的所有处理方法的默认实现(也有类似的适配器,用于创建编码器和解码器)。这些都是创建自定义处理器时,会经常调用的适配器:ChannelHandlerAdapter、ChannelInboundHandlerAdapter、ChannelOutInboundHandler、ChannelDuplexHandlerAdapter。*
当发送消息或者接受消息,Netty的数据转换就发生了。“入站消息”将从字节转化为一个Java对象;即“解码”;如果该消息是“出站”相反会发生“编码”,从一个Java对象转化为字节。原因很简单:网络数据是一系列的字节,因此需要类型的转换。
不同类型的抽象类用于提供编码器和解码器的,这取决于手头的任务。例如,应用程序可能并不需要马上将消息转为字节。相反,该消息将被转换 一些其他格式。一个编码器将仍然可以使用,但它也将衍生自不同的超类,一般情况下,基类将有一个名字类似 ByteToMessageDecoder 或 MessageToByteEncoder。在一种特殊类型的情况下,你可能会发现类似 ProtobufEncoder 和 ProtobufDecoder,用于支持谷歌的 protocol buffer。
严格地说,其他处理器可以做编码器和解码器能做的事。但正如适配器类简化创建通道处理器,所有的编码器/解码器适配器类 都实现自 ChannelInboundHandler 或 ChannelOutboundHandler。
对于入站数据,channelRead 方法/事件被覆盖。这种方法在每个消息从入站 Channel 读入时调用。该方法将调用特定解码器的“解码”方法,并将解码后的消息转发到管道中下个的 ChannelInboundHandler。
出站消息是类似的。编码器将消息转为字节,转发到下个的 ChannelOutboundHandler。
也许最常见的处理器是接收到解码后的消息并应用一些业务逻辑到这些数据。要创建这样一个 ChannelHandler,你只需要扩展基类SimpleChannelInboundHandler 其中 T 是想要进行处理的类型。这样的处理器,你将覆盖基类的一个或多个方法,将获得被作为输入参数传递所有方法的 ChannelHandlerContext 的引用。
在这种类型的处理器方法中的最重要是 channelRead0(ChannelHandlerContext,T)。在这个调用中,T 是将要处理的消息。 你怎么做,完全取决于你,但无论如何你不能阻塞 I/O线程,因为这可能是不利于高性能。
*阻塞操作
I/O 线程一定不能完全阻塞,因此禁止任何直接阻塞操作在你的 ChannelHandler, 有一种方法来实现这一要求。你可以指定一个 EventExecutorGroup。当添加 ChannelHandler 到ChannelPipeline。此 EventExecutorGroup 将用于获得EventExecutor,将执行所有的 ChannelHandler 的方法。这EventExecutor 将从 I/O 线程使用不同的线程,从而释放EventLoop。*