深入了解 Netty 原理篇
一. 概述
在网络编程中,最牛的 java 语言的开源框架就非 netty 莫属了,其隐藏复杂网络处理的逻辑从而抽象的提供易于使用的 API 的客户端/服务器框架,便于开发人员快速使用,而不用考虑各种场景的逻辑处理只关心业务层面的开发。
在了解 netty 原理时,可以先看之前写的有关 Selector 篇《Java Selector 模型》,可以更加快速的了解 netty 是怎么运作的。
在介绍 netty 时,也会介绍其采用的设计模式。
二. 使用
2.1 服务端
通过 netty 提起一个对外服务的代码如下:
2.2 客户端
通过 netty 提供一个访问服务的代码如下:
当我们初始化后拿到 bootstrap 对象后,就可以去通过其对象的 connect 方法去创建连接,返回 ChannelFuture 对象,我们就可以通过 ChannelFuture 对象中获取其内部的 Channel 对象,调用 Channel 对象中的 write 方法向服务端发送请求;
三. 原理
3.1 启动器
启动器的类图如下:
客户端以及服务端启动器都共同继承了 AbstractBootstrap 抽象类。其属性的具体用途介绍如下:
group 线程模型,也就是说当一个连接请求,或者交互请求,是如何处理的一种模型;其采用了一种叫做 Reactor 设计模式来实现。在客户端,其就一个线程池,一个线程处理所有的连接请求、发送请求、接收响应;在服务端,就是一个线程池,只负责接收连接请求;
channelFactory 创建 channel 的工厂类,在 netty 内封装了 jdk 原生中的 channel;例如 NioServerSocketChannel 以及 NioSocketChannel 等我们常见的 channel 实现类;
localAddress 就是 TCP 信息,即本地创建的 TCP。在服务端,就是对外服务的 IP 以及端口。而客户端,并没有使用该字段信息;
options 就是向目标 socket 设置相关属性的对象;
attrs 是向 channel 设置相关属性的对象
handler 是收到请求后处理器,如 IO 处理等,在客户端主要是协议解析,而在服务端主要的接受连接请求后创建 socket 对象;在服务端,我们一般不设置该属性;
3.1.1 客户端
客户端只记录两个属性,一个是服务端的目标地址;一个是配置对象 BootstrapConfig,其是一个建造类对象,最终相关的初始化信息都会落在 Bootstrap 对象中对应的属性字段;
我们将客户端连接服务端的过程梳理如下:
3.1.2 服务端
服务端的字段跟抽象类有点相似,只是父类中的是针对 ServerSocket 的,而 ServerBootstrap 类中的属性是针对 Socket 的;稍微需要特别说的是 childHandler 是针对协议解析的等功能;
我们将服务端连接服务端的过程梳理如下
其中稍微注意的初始化相关属性步骤,关键的代码如下:
3.2 线程模型
有看过聊聊Linux 五种IO模型文章的朋友都知道 IO 读写是非常慢的,在 netty 框架是采用了多路复用。另外可以学习《Scalable IO in Java》,可以对 reactor 模式有更多的认知,如果英文不行的话,可以阅读《传说中神一样的Reactor反应器模式》。
这里我简单整理出来几大关键步骤:
连接请求(Connection request)
接收请求(Read request)
解析请求(Decode request)
处理服务(Process service)
序列化(Encode reply)
发送响应结果(Send reply)
其服务端的交互图如下:
对应 netty 的接口 EventLoopGroup,类图如下:
分别的流程如下:
客户端接收响应跟上图是差不多的;
这里值得注意的是,netty 都是采用异步操作,其机制跟 jdk 中的 Future 原理差不多。当我查看 netty 的源码时候,都会看到 ChannelFuture,它们的关系如下图:
线程中负责读写操作,而 ChannelFuture 充当桥梁;业务代码可以往 ChannelFuture 添加对应的监听器,监听一旦已处理完则会触发监听器所对应的动作。
3.3 处理器以及管道
在 netty 框架中,处理器是非常重要的地位;上面介绍的线程模型是负责监听 socket 的相关事件,具体的事件的如何处理,还得交给处理器来处理;类图如下:
这里理清楚下面的几个接口的用途:
ChannelHandler 处理 IO 事件或者拦截 IO 操作,以及转发到下一个处理器操作。
ChannelOutboundHandler 接收 IO 操作的通知
ChannelInboundHandler 监听有关状态变更,可以触发对应的回调函数,如注册(Register)、读(Read)等状态的变更;
ChannelInboundInvoker、ChannelOutboundInvoker 是有关抽象出来的方法
ChannelPipeline 是管道接口
状图如下:
我们从几个动作所处的流程进行了解:
比如说服务器绑定端口所触发的逻辑,客户端连接所触发的流程,跟上面相似;可以去查阅;
3.3.1 处理器
处理是对 IO 传过来的信息进行处理,跟 tomcat 的过滤器有点相似。下面我所列的是常见的处理器;
IdleStateHandler 心跳机制,其通过 fireUserEventTriggered 来触发,所以业务端需要实现 ChannelInboundHandler 类来收集这些事件来做对应的处理。
FlowControlHandler 流量管控,但其比较粗糙。一般不会用;但是可以借鉴。
CorsHandler 跨域处理器
ProxyHandler 代理服务处理器
MessageToMessageCodec 类型转换处理器
ByteToMessageCodec 将 ByteBuf 转换成目标对象的类型处理器
ByteToMessageDecoder 将 ByteBuf 转换成目标对象的类型处理器
MessageToMessageDecoder 将匹配好的源对象转换成目标对象的类型转换处理器
AbstractTrafficShapingHandler 限流器
ServerBootstrapAcceptor 客户端连接处理器
ChannelInitializer 处理器追加处理,当启动器启动时,会将 ChannelInitializer 中的一些处理器追加到管道中;上面例子就可以看出来
我们常说的协议解析,主要介绍的 ByteToMessageDecoder。该类是抽象类,我们常用的定长报文解析 FixedLengthFrameDecoder,当然还有根据一个规则,如前面几个字节来确定一个完整的报文的长度是多少,如 LengthFieldBasedFrameDecoder,在 RocketMQ 就是使用该类来解析报文的;http 协议对应的 HttpObjectDecoder 下面的子类。redis 协议对应的 RedisDecoder。具体使用时具体找到 ByteToMessageDecoder 下面的实现类具体逻辑,现在对其有一个初步认知,后面继续加深认识。
除了协议解析之外,还有增加额外的功能,如限流 AbstractTrafficShapingHandler ,跨域处理 CorsHandler ,日志打印 LoggingHandler,压缩等。
在服务端,netty 默认会管道中添加 ServerBootstrapAcceptor 处理器,当客户端连接请求过来,会将该 socket 注册到 Selector 中。
四. 字节操作
字节操作也就是 netty 将 jdk 提供的 ByteBuffer 封装成了 ByteBuf 对象。便于操作;具体会在另外一篇详细介绍。
五.总结
通过上面的介绍,我们知道了启动器 AbstractBootstrap,启动器包含客户端、服务端。以及线程模型 EventLoopGroup 以及 EventLoop。以及管道 ChannelPipeline,以及 ChannelHandler,以及 ByteBuf。下面列出它们之间的关系;
版权声明: 本文为 InfoQ 作者【邱学喆】的原创文章。
原文链接:【http://xie.infoq.cn/article/69b8b26fc47d1a76ca39a31db】。文章转载请联系作者。
评论