文档章节

netty源码分析系列——概述

杨武兵
 杨武兵
发布于 2017/03/10 21:37
字数 2631
阅读 1013
收藏 8

前言

很久没有在开源中国上写博客了,今天回到这里继续写。今年打算研究几个开源项目的源码。今天从netty4.1.8的源码开始研究。本文是netty源码研究系列的开篇。

各种互联网、移动互联网应用的流行,从去年开始直播又变的特别热门,催生了当前云技术,微服务技术和物联网等技术的广泛应用,而这背后有一个最基础的能力就是网络通讯,netty作为当前最流行的一个java 网络nio的开发框架,是值得我们掌握的一项基本技能。

我之前简单使用过netty开发过一些东西,给我的直观感觉是事件驱动编程模型的与传统的链式调用编程模型有比较大的差异,从设计程序,理解程序到调试程序都有较大的差异。

本文开篇的一系列文章打算将netty4.1.8.Final的核心源码深入剖析一遍。目的是让自己深入掌握netty技术的使用,并且能够掌握它背后实现的原理及实现。

架构图

任何成熟一些的开源项目代码量都较大,我们该如何下手阅读理解这个项目呢?

先从宏观入手,了解该项目的架构及项目结构。再从中找到关键处入手。上图是netty官方公布的软件架构图我们先分析一下它的结构。

  1. Core(核心部分)。核心部分是底层的网络通讯的一些通用抽象。这部分内容是关键。
    1. Zero-Copy-Capable Rich Byte Buffer. 支持零拷贝能力的字节缓存。 这是netty字节流缓存的关键,支持“零拷贝”技术,以提升性能。
    2. Universal Communication API.通用的通讯API层,netty定义了一套抽象的通用通讯层api。
    3. Extensible Event Model。可扩展的事件模型。netty是一个典型的事件驱动模型场景。
  2. Transport Services(传输服务)。具体的网络传输能力的定义以及一些实现。
    1. Socket和数据报。
    2. HTTP。http 通道。
    3. In-VM Piple。虚拟机内部的管道流传输。
  3. Protocol Support(协议支持)。netty对于一些通用协议的编码解码实现。
    1. 支持常见的多种协议。例如http,redis,dns等协议。

包结构图

common:该项目是一个通用的工具类项目,几乎被所有的其它项目依赖使用,它提供了一些数据类型处理工具类,并发编程以及多线程的扩展,计数器等等通用的工具类。

buffer:该项目下是netty自行实现的一个ByteBuf字节缓冲区。该包的实现相对于jdk自带的ByteBuffer有很多优点,无论是API的功能,使用体验,性能都要更有,这些实现满足不同需求的实现类。它提供了一系列的抽象定义以及实现。该项目实现了netty架构图中的Zerocopy ByteBuf;

transport:该项目是网络传输通道的抽象和实现,定义通讯的统一通讯API,它统一了jdk的oio,nio和aio等多种编程接口,它提供了多种实现,包括native-epoll,sctp,udt和sctp等子项目实现不同的传输实现类型。该项是核心项目,实现了netty架构图中Transport Services、Universal Communication API和Extensible Event Model等多部分内容。

codec:该项目是协议编解码的抽象与实现。通过不同的子项目来实现http/http2/dns/redis/protocolbuffer等不同协议的支持。它与其它协议的客户端包和组件的区别是非常灵活,可以开发一些特定的场景的客户端或者服务端,该项目实现了netty架构图中的Protocal Support。

高层类图

上面的类图是忽略了接口或者所在的包,并且省略了很多属性和方法细节,只是为了体现netty的类组成结构以及类与类之间的关系,不是为了详细展现每个细节。下面我们对于这些接口或者类的职责和关系做一些分析。

Bootstrape&ServerBootstrape

这2个类都继承了AbstractBootstrap,因此它们有很多相同的方法和职责。它们都是启动器,能够帮助netty使用者更加方便地组装和配置netty,也可以更方便地启动netty应用程序,比使用者自己从头去将netty的各部分组装起来要方便地多,降低了使用者的学习和使用成本,它是我们使用netty的入口和最重要的API,可以通过它来连接到一个主机和端口上,也可以通过它来绑定到一个本地的端口上,它们两者之间相同之处要大于不同。

它们和其它类之间的关系是它将netty的其它各类进行组装和配置,它会组合和直接或间接依赖其它的类。

Boostrape往往是用于启动一个netty客户端或者UDP的一端。它通常使用connet()方法连接到远程的主机和端口,当然也可以通过bind()绑定本地的一个端口,它仅仅需要使用一个EventLoopGroup。

ServerBoostrape往往是用于启动一个netty服务端。它通常使用bind()方法绑定本地的端口上,然后等待客户端的连接,它使用两个EventLoopGroup对象(当然这个对象可以引用同一个对象),第一个用于处理它本地socket连接的io事件处理,而第二个责负责处理远程客户端的io事件处理。

该部分的详细源码分析见博客——https://my.oschina.net/ywbrj042/blog/868798

EventLoopGroup&EventLoop

Netty是一个事件驱动型框架,处理网络io事件是重要的组成部分,而EventLoopGroup和EventLoop这个接口是承担这一职责的重要角色。

EventLoop负责处理注册到其上的Channel的所有I/O事件循环处理,同一个实例可以处理一个或者多个Channel的I/O事件,这取决于它的具体实现细节。EventLoop又是一个EventLoopGroup子接口,可以理解为一种特殊的EventLoopGroup,这个设计有点儿特殊,暂时还不能理解作者的精髓。(亦或者本身这就是作者的一个败笔也未可知)

EventLoopGroup是一个EventLoop的组,它可以获取到一个或者多个EventLoop对象,因此它提供了迭代出EventLoop对象的方法。

Channel

Channel是netty中定义的一个网络连接通道。它是连接到网络套接字或者组件的输入或输出操作,例如读字节流,写字节流,连接到远程服务器和绑定本机的端口等网络操作。

Channel提供了用户这些操作接口:

  • 查看当前连接通道的状态。例如是否打开,是否已连接等。
  • 连接通道的配置ChannelConfig对象。例如连接的接收缓冲区大小。
  • 连接通道支持的I/O操作。例如读,写,连接,绑定等。
  • 获得关联的处理器链ChannelPipeline对象。负责处理所有的I/O事件和与该通道关联的请求。

Channel的所有I/O操作都是异步的,不是直接返回一个结果,而是返回一个ChannelFuture对象,用户可以在该对象上绑定监听器,监听事件完成或者错误的处理。

Channel是分层次的,通过parent()方法可以获得它的父通道。这取决于它的具体实现逻辑。例如SocketChannel是由ServerSocketChannel接收并且创建的,则它的parent方法则可以获取到一个ServerSocketChannel对象。

Channel是一个通用且抽象的连接通道,它不会保留那些具体transport技术特有的I/O操作方法,因此如果要调用具体的操作,则需要强制类型转换为它的具体子接口或者实现类,例如DatagramChannel类型的子接口是数据报(datagram transport)传输通道的扩展接口,将对象转为改类型的引用就可以调用它的join,leave和block等特有方法。

ChannelPipeline

ChannelPipeline是一个负责处理或拦截输入事件和输出操作的处理器链表。它让用户可以完全控制事件的处理方式及流程,是一个典型的责任链模式的实现。

每个Channel都有它关联的ChannelPipeline对象,是在连接通道Channel被创建的时候会自动创建一个处理器链ChannelPipeline对象,

*                                                 I/O Request
*                                            via {@link Channel} or
*                                        {@link ChannelHandlerContext}
*                                                      |
*  +---------------------------------------------------+---------------+
*  |                           ChannelPipeline         |               |
*  |                                                  \|/              |
*  |    +---------------------+            +-----------+----------+    |
*  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
*  |    +----------+----------+            +-----------+----------+    |
*  |              /|\                                  |               |
*  |               |                                  \|/              |
*  |    +----------+----------+            +-----------+----------+    |
*  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
*  |    +----------+----------+            +-----------+----------+    |
*  |              /|\                                  .               |
*  |               .                                   .               |
*  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
*  |        [ method call]                       [method call]         |
*  |               .                                   .               |
*  |               .                                  \|/              |
*  |    +----------+----------+            +-----------+----------+    |
*  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
*  |    +----------+----------+            +-----------+----------+    |
*  |              /|\                                  |               |
*  |               |                                  \|/              |
*  |    +----------+----------+            +-----------+----------+    |
*  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
*  |    +----------+----------+            +-----------+----------+    |
*  |              /|\                                  |               |
*  +---------------+-----------------------------------+---------------+
*                  |                                  \|/
*  +---------------+-----------------------------------+---------------+
*  |               |                                   |               |
*  |       [ Socket.read() ]                    [ Socket.write() ]     |
*  |                                                                   |
*  |  Netty Internal I/O Threads (Transport Implementation)            |
*  +-------------------------------------------------------------------+

上图展示了一个I/O请求是如何被处理器链执行的完整过程。

提供了往里面加入ChannelHandler对象的操作,还有迭代每个ChannelHandler对象的操作。

ChannelHandler

连接通道处理器类。它处理I/O事件或者拦截I/O操作,并且转发到处理器链中的下一个处理器。

改接口自身提供的操作并不多,但是从它扩展了一系列的子接口和各种实现类。

ChannelInboundHandler是专门负责处理输入I/O事件的扩展接口。它的实现类是还包括一系列的Decoder类,对输入字节流进行解码。

ChannelOutboundHandler是专门负责处理输出I/O事件的扩展接口。它的实现类是还包括一系列的Encoder类,对输入字节流进行编码。

ChannelDuplexHandler可以同时处理输入和输出事件。

还有其它的一系列的抽象实现Adapter,还有一些具体的实现,用于实现具体的协议。

ChannelInitializer

ChannelInitializer是一个特殊的ChannelInboundHandlerAdapter抽象类,通过该初始化器可以将用户自定义的ChannelHandler对象加入到处理器链中,而它字节也是一个特殊的ChannelInboundHandler实现类,在初始化结束之后,它会被自己从处理器链中删除,它是一个没有实际处理事件意义的处理器类,是专门负责初始化的。这种设计非常奇怪。

参考资料

1.《netty in action》。

2.netty-4.1.8.Final版本源码。

© 著作权归作者所有

共有 人打赏支持
杨武兵

杨武兵

粉丝 255
博文 61
码字总数 123254
作品 1
昌平
架构师
私信 提问
Java系列文章(全)

JVM JVM系列:类装载器的体系结构 JVM系列:Class文件检验器 JVM系列:安全管理器 JVM系列:策略文件 Java垃圾回收机制 深入剖析Classloader(一)--类的主动使用与被动使用 深入剖析Classloader(二...

www19
2017/07/04
0
0
C语言自学完备手册(22)——输入输出

版权声明: https://blog.csdn.net/lfdfhl/article/details/82897916 自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–o...

谷哥的小弟
09/29
0
0
早前学习Java记录

Spring 对 iBATIS 的支持】 Spring 通过 DAO 模式,提供了对 iBATIS 的良好支持。 SqlMapClient:是 iBATIS 中的主要接口,通过 xml 配置文件可以让 Spring 容器来管理 SqlMapClient 对象的创...

大风厂蔡成功
2016/07/10
43
0
《深入探索Netty原理及源码分析》文集小结

写在2017年末尾,翻看文集的第一篇文章已经是三个月前的事了,也没想过这文集会写那么久,这么慢。。。 Netty文集中的文章主要都是我学习过程的笔记,写博客的主要目的是为了通过输出来倒逼输...

tomas家的小拨浪鼓
2017/12/30
0
0
spark2.1.0之源码分析——RPC管道初始化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/81197447 提示:阅读本文前最好先阅读: 《Spark2.1.0之内置RPC框架》 《spark2.1....

泰山不老生
07/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

day150-2018-11-17-英语流利阅读-待学习

歪果仁也疯狂:海外版抖音的征途 毛西 2018-11-17 1.今日导读 海外版抖音 TikTok 于 2017 年 5 月上线海外,至今覆盖全球 150 多个国家和地区,月活跃用户数已突破 5 亿。然而,“出海”的抖...

飞鱼说编程
今天
9
0
分布式学习最佳实践:从分布式系统的特征开始(附思维导图)

什么是分布式系统 回到顶部   分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法...

dragon_tech
今天
4
0
TOKEN设计

TOKEN设计 Api_Token 首先需要知道API是什么? API(Application Programming Interface)即应用程序接口。你可以认为 API 是一个软件组件或是一个 Web 服务与外界进行的交互的接口。而我们在...

DrChenXX
今天
3
0
浅谈“李氏代换”——从纪念金庸和斯坦李说起

李氏代换(LSP)简介 李氏代换是软件设计的一个原则,又名依赖倒转原则或依赖倒置原则,其衍生原则有接口分离原则等。该原则由Barbara Liskov于1988年提出。 该原则指出,程序中高级别的元素...

SamYjy
今天
36
0
JavaScript实现在线websocket WSS测试工具 -toolfk程序员工具网

本文要推荐的[ToolFk]是一款程序员经常使用的线上免费测试工具箱,ToolFk 特色是专注于程序员日常的开发工具,不用安装任何软件,只要把内容贴上按一个执行按钮,就能获取到想要的内容结果。T...

toolfk
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部