从高层次的角度来看Netty, 它主要为需要开发高性能应用的开发者解决了“技术”的和“体系结构”的问题。首先,它的基于 Java NIO 的异步的和事件驱动的实现,保证了高负载下应用程序
性能的最大化和可伸缩性。其次, Netty 也包含了一组设计模式,将应用程序逻辑从网络层解耦,简化了开发过程,同时也最大限度地提高了可测试性、模块化以及代码的可重用性。
为了可以更好的研究 Netty,本文主要对Netty的组件做一个简单的描述,以及从高层次的角度来了解各个组件是如何协作的。
Netty中主要组件包括:
- Channel:代表了一个链接,与EventLoop一起用来参与IO处理。
- ChannelHandler:为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
- ChannelPipeline:提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站
和出站事件流的 API。
- EventLoop:Channel处理IO操作,一个EventLoop可以为多个Channel服务。
- EventLoopGroup:会包含多个EventLoop。
上述组件的关系结构如下图所示:
在Java中(Socket类),基本的 I/O 操作(bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的功能。 Netty 的 Channel 接
口所提供的 API降低了直接使用 Socket 类的复杂性。Netty中也提供了使用多种方式连接的Channel:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。
EventLoop主要用于处理连接的生命周期中所发生的事件,它实现了Netty的线程模型部分的功能,线程模型指定了操作系统/编程语言/框架或者应用程序的上下文中的线程管理的方式。如何以及何时创建线程将对应用程序的性能产生显著的影响。详细细节将在后续的博文进行说明,本处只做简单描述。
Channel&EventLoop&EventLoopGroup的协作方式大致如下:
对于开发一个Netty的应用而言,主要开发的组件可能就是 ChannelHandler, 它充当了所有处理入站和出站数据的应用程序逻辑的容器,根据数据流向的不同,ChannelHandler可以分为ChannelInboundHandler以及ChannelOutboundHandler.
ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站
和出站事件流的 API。当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。
ChannelHandler 安装到 ChannelPipeline 中的过程如下所示:
1. 一个ChannelInitializer被注册到了ServerBootstrap中;
2. 当 ChannelInitializer.initChannel()方法被调用时, ChannelInitializer
将在 ChannelPipeline 中安装一组自定义的 ChannelHandler;
3. ChannelInitializer 将它自己从 ChannelPipeline 中移除
事件进入ChannelPipline时,会被定义的ChannelHandler顺序的进行处理,如下图所示:
从一个客户端应用程序的角度来看,如果事件的运动方向是从客户端到服务器端,那么我们称这些事件为Outbound的,反之则称为Inbound的。需要注意的是对于inbound操作而言,处理的顺序为从头到尾,outbound的处理顺序为从尾到头。上图中Inbound的处理顺序为(1,2).Outbound的处理顺序为(3,4).
当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandler
Context,其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可
以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。
==在 Netty 中,有两种发送消息的方式。你可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。==
首先要说明的是ChannelInboundHandle 和ChannelOutboundHandle 都继承自ChannelHandler,将两个类别的 ChannelHandler都混合添加到同一个 ChannelPipeline 中时,Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。
对于ChannelHandler的实现类而言,可能不需要关注事件处理周期的每个环节,如果要把Inbound或是Outboud接口的每个方法都实现,就会额外的带来很多的工作量,Netty对于该种情况提供了几种Adapter的解决方案:
当通过 Netty 发送或者接收一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式,通常是一个 Java 对象。如果是出站消息,则会发生
编码:将从它的当前格式被编码为字节。
Netty默认已经提供了一堆的编/解码器,一般需求已经可以满足,如果不满足可以通继承ByteToMessageDecoder 或 MessageToByteEncoder来实现自己的编/解码器。通过查看类的继承结构可以看出 Netty 提供的编码器/解码器适配器类都实现了 ChannelOutboundHandler 或者 ChannelInboundHandler 接口。
对于解码器Handler而言,其重写了channelRead方法,对于每个从入站
Channel 读取的消息,这个方法都将会被调用。随后,它将调用由解码器所提供的 decode()方法,并将已解码的字节转发给 ChannelPipeline 中的下一个 ChannelInboundHandler。OutboundHandler采用相反的处理方式。
你的应用程序会利用一个 ChannelHandler 来接收解码消息,并对该数据应用业务逻辑。 这种情况下,你只需要扩展基类 SimpleChannelInboundHandler,其中 T 是你要处理的消息的 Java 类型。
在这种类型的 ChannelHandler 中, 最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞当前的 I/O 线程之外,其具体实现完全取决于业务需要。
Netty 的引导类为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定的端口,或者将一个进程连接到另一个运行在某个指定主机的指定端口上的进程。
有两种类型的引导:
1. 用于客户端(Bootstrap)
2. 用于服务器(ServerBootstrap)。
需要注意的是,引导一个客户端只需要一个 EventLoopGroup,但是一个ServerBootstrap 则需要两个(也可以是同一个实例)
下图说明了Server端两个EventLoopGroup的用途:
第一个EventLoopGroup用来专门负责绑定到端口监听连接事件,而把第二个EventLoopGroup用来处理每个接收到的连接。
对于Server端,如果仅由一个EventLoopGroup处理所有请求和连接的话,在并发量很大的情况下,这个EventLoopGroup有可能会忙于处理已经接收到的连接而不能及时处理新的连接请求,用两个的话,会有专门的线程来处理连接请求,不会导致请求超时的情况,大大提高了并发处理能力