Netty in action—Netty组件和设计

来源:互联网 发布:小米软件系统 编辑:程序博客网 时间:2024/06/14 10:50

Channel,EventLoop和ChannelFuture

这三者可以看作是Netty网络架构的抽象

  • Chennel-Sockets
  • EvnetLoop-流控(Control flow),多线程,并发
  • ChannelFuture - 异步通知

Channel接口

基本的IO操作(bind(),connect(),read(),write())都是基于底层的网络传输。在Java网络编程中,基础构建是Socket类,但是直接基于Socket进行编程会很繁琐。Netty的Channel接口提供的API能大大地减小直接对Socket进行编程的复杂性。另外,Channel又是很多实现了特定需求的Channel的顶层抽象接口,这些Channel接口的实现类有:

  • EmbeddedChannel
  • LocalServerChannel
  • NioDatagramChannel
  • NioSctpChannel
  • NioSocketChannel

EventLoop接口

EventLoop用来处理一个连接(connection)生命周期内发生的事件。下图在高层次上描述了Channel、EventLoop、Thread和EventLoopGroup之间的关系。

这些关系有:

  • EventLoopGroup包含一个或多个EventLoop
  • 一个EvnetLoop实例绑定到了一个线程在它(EvnetLoop)的生命周期内。
  • 所有被EventLoop操作的IO事件都是在它专有的线程中处理
  • 一个Channel在它的生命周期中被注册到一个EventLoop中,然后这个EventLoop就会为这个Channel处理所有的IO操作
  • 一个EventLoop实例能注册多个Channel
    这里写图片描述

注意在这种设计中,给定Channel的IO操作都是在同一个线程中执行,无形中就不需要进行同步。

ChannelFuture接口

Netty中的所有IO操作都是异步的。因为一个操作的结果可能不会马上返回,所以我们需要一种能够在未来获取它的结果的方式。为此,Netty提供了ChannelFuture,它的addListener()方法注册了一个ChannelFutureListener,通过这个ChannelFutureListener就能在操作完成(不管有没有成功)时得到通知。

ChannelHandler和ChannelPipeline

ChannelHandler接口

站在应用程序开发者的角度来看,Netty主要的组件就是ChannelHandler,它作为所有为传入和传出数据提供处理的应用业务逻辑的容器。通常实现它(或继承实现了它的类)来提供业务逻辑。这是可能的因为ChannelHandler的方法会被网络事件触发。实际上,ChannelHandler能被用于任意类型的操作,比如,数据的格式转换或处理操作过程中抛出的异常。

你通常需要实现的是ChannelHandler的子接口ChannelInboundHandler。这个子接口能接收你业务逻辑需要处理的数据或者事件。当你要发送一个回复给客户端的时候,你能从ChannelInboundHandler中flush数据。你的业务逻辑通过会依赖于多个ChannelInboundHandler

ChannelPipeline接口

ChannelPipeline为一连串的ChannelHandler提供容器同时定义了用于传播链中传入数据和传出数据事件流的API。Channel被创建后会自动的分配到它自己的ChannelPipeline

ChannelHandler通过如下的步骤被加载到ChannelPipeline中:

  • 一个ChannelInitializer的实现注册到ServerBootstrap
  • ChannelInitializer.initChannel()方法被调用,ChannelInitializer装载了一系列自定义的ChannelHandlers到pipeline中。
  • ChannelInitializer把它自己从ChannelPipeline中移除掉
    ChannelHandler具有广泛的用途,你能把它想成一个为任何处理在ChannelPiple中流通(传入传出)的事件(包括数据)的代码的通用容器(generic container)。下图说明了ChannelInboundHandlerChannelOutboundHandler继承自ChannelHandler的关系。

这里写图片描述
事件通过在初始化或启动应用期间加载的ChannelHandler在pipeline中传递,ChannelHandler接受事件并执行在其中实现了的业务逻辑,并将数据传递给(handler)链中的下一个handler。这些handler根据它们被添加的顺序来执行。

这里写图片描述

上图展示了Netty应用中输入(数据)流和输出(数据)流的区别。对于客户端来说,从客户端到服务器事件就是传出去(outbound)的反之就是传进来(inbound)的。

inbound和outbound handler都能安装到同一个pipeline。如果读到一条消息或者其他传入(inbound)事件,它会从这条pipeline的头部(head)开始然后传递给第一个ChannelInboundHandler。这个handler可能会也可能不会真的修改数据,这取决于它的具体函数,然后数据会传递给下一个handler。最终,数据回到达pipeline的末尾(tail),此时所有的处理都结束了。

数据传出(outbound)的移动(也就是数据被写)与传入同理。这种情况下,数据从尾部流到头部。

因为传出数据和传入数据是有区别的,你可能想知道当这两种不同的操作都混在了同一个ChannelPipeline时会发生什么。虽然inbound和outbound handler都继承了ChannelHandler,Netty提供了分别的实现(ChannelInboundHandler好ChannelOutbouondHandler)同时确保数据只会在同一个方向的handler中传递。

当一个ChannelHandler被添加到ChannelPipeline中,它会被分配一个代表ChannelHandlerChannelPipeline之间绑定关系的一个ChannelHandlerContext。尽管可以通过这个Context获取底层的Channel实例,但是它主要用来写传出数据(write outbound data)。

在Netty中有两种发送数据的方式。你既可以直接写到Channel里面又可以写到与ChannelHander相关的ChannelHandlerContext中。前者会使数据从ChannelPipeline的末尾开始传输,后者会使数据从ChannelPipeline中下一个handler开始。

更深入的了解ChannelHandler

正如我们前面说到的,Netty提供了不同类型的ChannelHandler,它们的功能很大程度上是由它们的超类决定。Netty以适配器(adapter)类的方式提供了许多默认的handler实现以简化应用业务逻辑的开发。你已经知道在一个pipeline中每一个ChannelHandler都负责把事件传递给下一个handler。这些适配器类(以及它们的子类)会自动这么做,因此你可以只重写你感兴趣的方法和事件。

接下来我们来了解3个ChannelHandler的子类:encoders,decoders以及SimpleChannelInboundHandler(一个ChannelInboundHandlerAdapter的子类)。

encoder和decoder

当你通过Netty收发消息时,一个数据的转换就会发生。传入的消息会被解码(decode),从字节转换成其他的格式,通常是一个Java对象。如果是传出的消息,那么就会把数据从当前的格式转换为字节。因为网络数据总是字节流的。

Netty为编码解码提供了大量与特定需求相关的抽象类。比如,你的应该可能会使用一个中间(intermediate)格式,不需要将消息马上转换为字节。你仍然需要一个编码器(encoder),但它可从不同的超类中继承。为了决定使用哪一个,你可以应用一个简单的命名约定(apply a simple naming convention)。

通常,基类会有像ByteToMessageDecoderMessageToByteEncoder的类名。在具体的实现类中,有与Protobuf相关的ProtobufEncoderProtobufDecoder

所有的编码解码适配器类都实现了ChannelInboundHandlerChannelOutboundHandler

对于传入数据来说,你会发现channelRead方法或事件被重写了。这个方法会被从传入数据(inbound)Channel读到的消息调用。然后会通过提供的decoder调用decode()方法然后将解码了的字节(解码成Java对象)传递给下一个ChannelInboundHandler

对于传出数据来说就是相反的,一个encoder将消息转换成字节然后传递给一下handler。

SimpleChannelInboundHandler抽象类

通常你的应用需要使用一个handler来接收一个解码了的数据然后应用一些业务逻辑。为了创建这样只有个handler,你只需要继承SimpleChannelInboundHandler<T>T就是你想要处理的数据的Java类型。

这个类中最重要的方法是channelRead0(ChannelHandlerContext ctx,T t)。它的实现完全取决于你,除非需要当前IO线程不被阻塞。

bootstrap

Nettyd的bootstrap类为配置一个应用网络层的容器,这涉及到给一个特定的端口号绑定一个程序(process)(通常是用于启动服务器);或连接一个程序到另一个在特定的主机和端口号运行的程序(通常是用于启动一个客户端)。也就是监听连接请求或建立连接。

严格的说术语连接指的是面向连接协议如TCP,它可以保证传输数据的有序性。

因此,有两种bootstrap:一种是为客户端设计的,就叫Bootstrap;另一种是为服务端设计的(ServerBootstrap)。

下表比较了这两种类的区别:

分类 Bootstrap ServerBootstrap 网络功能 连接到远程的主机和端口 绑定到一个本地端口 EventLoopGroup的数量 1 2

我们重点解释一下第二个不同,启动一个客户端只需要一个EventLoopGroup,但服务端需要两个(可以使同一个实例)。为啥子呢?

一个服务器需要两个不同的Channel集合。第一个集合包含一个ServerChannel代表服务器自己绑定了本地端口的监听socket。第二个集合会包含所有被创建来处理客户端连接的Channel。下图展示了这个模型。

这里写图片描述

ServerChannel相关的EventLoopGroup(左边的)分配EventLoop来处理为连接请求创建ServerChannel,一旦一个连接被接收,第二个EventLoopGroup就为这个Channel分配一个EventLoop