揭开netty神秘面纱_Netty 源码(ChannelHandler 死磕)

疯狂创客圈   经典图书 : 《Netty Zookeeper Redis 高并发实战》    面试必备 +  面试必备 + 面试必备

疯狂创客圈   经典图书 : 《SpringCloud、Nginx高并发核心编程》   大厂必备 +  大厂必备+ 大厂必备

无编程不创客,疯狂创客圈,一大波编程高手正在交流、学习中!

精进篇:netty源码死磕5  - 揭开 ChannelHandler 的神秘面纱

目录

1. 前言2. Handler在经典Reactor中的角色

3. Handler在Netty中的坐标位置

4. Netty中Handler的类型

1.1. ChannelInboundHandler入站处理器

1.2. ChannelOutboundHandler出站处理器5. 揭开Pipeline的神秘面纱

6. Handler的上下文环境

7. Handler的注册

7.1. 第一步:包裹

7.2. 加入链表并注册完成回调事件

7.3. 回调添加完成事件

8. 小结

1. 前言

Reactor模式是Netty的基础和灵魂,掌握了经典的Reactor模式实现,彻底掌握Netty就事半功倍了。《Reactor模式(netty源码死磕3)》对Reactor模式的经典实现,进行了详细介绍。作为本文的阅读准备,可以去温习一下。

Reactor模式的两个重要的组件,一个是Reactor反应器,在Netty中的对应的实现是EventLoop,在文章《EventLoop(netty源码死磕4)》中,已经有了非常详细的介绍。

此文聚焦于Reactor模式的另一个重要的组成部分Handler。

2. Handler在经典Reactor中的角色

在Reactor经典模型中,Reactor查询到NIO就绪的事件后,分发到Handler,由Handler完成NIO操作和计算的操作。

Handler主要的操作为Channel缓存读、数据解码、业务处理、写Channel缓存,然后由Channel(代表client)发送到最终的连接终端。

3. Handler在Netty中的坐标

经典的Reactor模式,更多在于演示和说明,仅仅是有一种浓缩和抽象。

由于Netty更多用于生产,在实际开发中的业务处理这块,主要通过Handler来实现,所以Netty中在Handler的组织设计这块,远远比经典的Reactor模式实现,要纷繁复杂得多。

在分析Handler之前,首先回顾一下Netty中的Channel。在《EventLoop(netty源码死磕4)》中,已经有详细的说明。一个Netty Channel对应于一个Client连接,内部封装了一个Java NIO SelectableChannel 可查询通道。

再回到Handler。

Hander的根本使命,就是处理Channel的就绪事件,根据就绪事件,完成NIO处理和业务操作。比方Channel读就绪时,Hander就开始读;Channel写就绪时,Hander就开始写。

4. Netty中Handler的类型

从应用程序开发人员的角度来看,Netty的主要组件是ChannelHandler,所以,对ChannelHandler的分类,也是从应用开发的角度来的。

从应用程序开发人员的角度来看,数据有入站和出站两种类型。

这里的出站和入站,不是网络通信方面的入站和出站。而是相对于Netty Channel与Java NIO Channel而言的。

数据入站,指的是数据从底层的Java NIO channel到Netty的Channel。数据出站,指的是通过Netty的Channel来操作底层的 Java NIO chanel。

从入站和出战的角度出发,Netty中的ChannelHandler主要由两种类型,ChannelInboundHandler和ChannelOutboundHandler。

1.1. ChannelInboundHandler入站处理器

当Java NIO事件进站到Channel时,产生一的一系列事件将由ChannelHandler所对应的API处理。

当查询到Java NIO底层Channel的就绪事件时,通过一系列的ChannelInboundHandler处理器,完成底层就绪事件的处理。比方说底层连接建立事件、底层连接断开事件、从底层读写就绪事件等等。

啰嗦一下,入站(inbound)处理通常由底层Java NIO channel触发,主要事件如下:

1. 注册事件 fireChannelRegistered。

2. 连接建立事件 fireChannelActive。

3. 读事件和读完成事件 fireChannelRead、fireChannelReadComplete。

4. 异常通知事件 fireExceptionCaught。

5. 用户自定义事件 fireUserEventTriggered。

6. Channel 可写状态变化事件 fireChannelWritabilityChanged。

7. 连接关闭事件 fireChannelInactive。

1.2. ChannelOutboundHandler出站处理器

当需要Netty Channel需要操作Java NIO底层Channel时,通过一系列的ChannelOutboundHandler处理器,完成底层操作。比方说建立底层连接、断开底层连接、从底层Java NIO通道读入、写入底层Java NIO通道等。ChannelOutboundHandler是一个接口,主要操作如下图所示:

啰嗦一下,出站(inbound) Handler通常是Netty channel操作底层Java NIO channel,主要操作如下:

1. 端口绑定 bind。

2. 连接服务端 connect。

3. 写事件 write。

4. 刷新时间 flush。

5. 读事件 read。

6. 主动断开连接 disconnect。

7. 关闭 channel 事件 close。

至此,Netty中的两大处理器的类型,就已经说得很清楚了。

再说说Handler和Channel的关系。

打个比方,如果Hander是太阳系的行星,那么Channel就是太阳系的恒星。Hander的服务对象和公转的轴心,就是Channel。

这可能是最为不恰当的一个比方,但是说的是事实。

5. 揭开Pipeline的神秘面纱

一个Channel在数量上,肯定不止拥有一个Handler。 如何将杂乱无章的Handler,有序的组织起来呢?

来了一个Handler的装配器——Pipeline。

Pipeline是何方神圣呢?

先揭一下神秘面纱:

Netty中, 使用一个双向链表,将属于一个Channel的所有Handler组织起来,并且给这个双向链表封装在一个类中,再给这个类取了一个非常牛逼的名字,叫做ChannelPipeline。

为什么这个名字很牛逼呢?

实际上这里用了Java中一种非常重要的设计模式,Pipeline设计模式。后面将用专门的文章,来介绍这种牛逼模式。

回到主题:

一个Channel,仅仅一个ChannelPipeline。该pipeline在Channel被创建的时候创建。ChannelPipeline相当于是ChannelHandler的容器,它包含了一个ChannelHander形成的列表,且所有ChannelHandler都会注册到ChannelPipeline中。

6. Handler的上下文环境

在Netty的设计中,Handler是无状态的,不保存和Channel有关的信息。打个不恰当的比方,Handler就像国际雇佣军一样,谁给钱,给谁打仗。Handler的目标,是将自己的处理逻辑做得很完成,可以给不同的Channel使用。

与之不同的是,Pipeline是有状态的,保存了Channel的关系。

于是乎,Handler和Pipeline之间,需要一个中间角色,把他们联系起来。这个中间角色是谁呢?

它就是——ChannelHandlerContext 。

所以,ChannelPipeline 中维护的,是一个由 ChannelHandlerContext 组成的双向链表。这个链表的头是 HeadContext, 链表的尾是 TailContext。而无状态的Handler,作为Context的成员,关联在ChannelHandlerContext 中。在对应关系上,每个 ChannelHandlerContext 中仅仅关联着一个 ChannelHandler。

我们继续用源码说话。

Context的双向链表的主要代码,在 AbstractChannelHandlerContext类中。该类主要包含一个双向链表节点的前置和后置节点引用 prev、next,以及数据引用 handler,相当于链表数据结构中的 Node 节点。

部分关键源码节选如下:

// ChannelHandler 首位指针

final AbstractChannelHandlerContext head;

final AbstractChannelHandlerContext tail;

// pipeline 所属 channel

private final Channel channel;

private final ChannelFuture succeededFuture;

private final VoidChannelPromise voidPromise;

private final boolean touch = ResourceLeakDetector.isEnabled();

protected DefaultChannelPipeline(Channel channel) {

this.channel = ObjectUtil.checkNotNull(channel, "channel");

succeededFuture = new SucceededChannelFuture(channel, null);

voidPromise = new VoidChannelPromise(channel, true);

tail = new TailContext(this);

head = new HeadContext(this);

head.next = tail;

tail.prev = head;

}

7. Handler的注册

Handler是如何注册到Pipeline中的呢?

1.3. 第一步:包裹

加入到Pipeline之前,在Pipeline的基类DefaultChannelPipeline中,首先对Handler进行包裹。

代码如下:

// 使用 AbstractChannelHandlerContext 包裹 ChannelHandler

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {

return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);

}

1.4. 加入链表并注册完成回调事件

1. 构建了 AbstractChannelHandlerContext 节点,并加入到了链表尾部。

2. 如果 channel 尚未注册到 EventLoop,就添加一个任务到 PendingHandlerCallback 上,后续channel 注册完毕,再调用 ChannelHandler.handlerAdded。

3. 如果已经注册,马上调用 callHandlerAdded0 方法来执行 ChannelHandler.handlerAdded 注册完成的回调函数。

代码如下:

@Override

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {

final AbstractChannelHandlerContext newCtx;

synchronized (this) {

// 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常

checkMultiplicity(handler);

// 构建 AbstractChannelHandlerContext 节点

newCtx = newContext(group, filterName(name, handler), handler);

// 添加到链表尾部

addLast0(newCtx);

// registered 为 false 表示 channel 尚未注册到 EventLoop 上。

// 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded

if (!registered) {

newCtx.setAddPending();

callHandlerCallbackLater(newCtx, true);

return this;

}

// registered 为 true,则立即调用 ChannelHandler.handlerAdded

EventExecutor executor = newCtx.executor();

// inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。

if (!executor.inEventLoop()) {

newCtx.setAddPending();

executor.execute(new Runnable() {

@Override

public void run() {

callHandlerAdded0(newCtx);

}

});

return this;

}

}

callHandlerAdded0(newCtx);

return this;

}

@Override

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {

final AbstractChannelHandlerContext newCtx;

synchronized (this) {

// 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常

checkMultiplicity(handler);

// 构建 AbstractChannelHandlerContext 节点

newCtx = newContext(group, filterName(name, handler), handler);

// 添加到链表尾部

addLast0(newCtx);

// registered 为 false 表示 channel 尚未注册到 EventLoop 上。

// 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded

if (!registered) {

newCtx.setAddPending();

callHandlerCallbackLater(newCtx, true);

return this;

}

// registered 为 true,则立即调用 ChannelHandler.handlerAdded

EventExecutor executor = newCtx.executor();

// inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。

if (!executor.inEventLoop()) {

newCtx.setAddPending();

executor.execute(new Runnable() {

@Override

public void run() {

callHandlerAdded0(newCtx);

}

});

return this;

}

}

callHandlerAdded0(newCtx);

return this;

}

1.5. 回调添加完成事件

添加完成后,执行回调方法如下:

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {

try {

ctx.handler().handlerAdded(ctx);

ctx.setAddComplete();

} catch (Throwable t) {

…….

}

会执行handler的handlerAdded 方法,这是一个回调方法。添加完成后的回调代码,基本上写在这里。

8. 小结

至此,牛逼的Netty Handler和Netty Reactor 介绍完了。

对于Pipeline模式和基于Pipeline的Netty 入站和出站的事件传输机制,【疯狂创客圈】在后面的系列死磕文章,会做一个非常精彩的介绍。

无编程不创客,无案例不学习。疯狂创客圈,一大波高手正在交流、学习中!

疯狂创客圈 Netty 死磕系列10多篇深度文章:【博客园 总入口】  QQ群:104131248

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言链表集合求并集用字母表示,c语言实现的链表集合的并集与交集

c语言,链表,集合求并集,交集#include#includetypedefintDatatype;//定义链表的节点typedefstructLNode{Datatype data;LNode *next;}LNode,*LinkList;boolInitLink(LinkList&L) //初始化链表{L (LinkList)malloc(sizeof(LNode));if(LNULL){return false;}L->nextNULL;re…

python下载文件加上日期_Python实现给下载文件显示进度条和下载时间代码

本篇文章小编给大家分享一下Python实现给下载文件显示进度条和下载时间代码,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看。 该模块调用了三个库: 1.os 2.requests 3.…

c# 计算空格宽度像素_用pythonnet为计算机视觉做图像整理

中国的.NETer是国内技术的另类,当他们强调.NET也可以做啥啥时都会给别的技术藐视,毕竟主流都不用.NET。本人这几年其实花在.NET时间也少,都投入在Python/Go社区。可我还是有点工作外的寄托,就是让.NET也有一个很好的推广&#xff…

C语言笔试不好应该转专业吗,你认为大学里什么学科“难学”?过来人说出几门,考试难补考更难...

原标题:你认为大学里什么学科“难学”?过来人说出几门,考试难补考更难文/晓宁说教育2020届的大一新生们的第一个学期已经结束了,经过了一个学期的学习和生活,相信很多学生都对自己的大学有了一定的规划。尽管进入大学之…

mysql默认值无效_MySQL开发规范

一、基础规范1) 使用InnoDB存储引擎2) 数据库字符集使用UTF8,校对字符集使用utf8_general_ci3) 所有表、字段都尽量添加注释4) 库名、表名、字段名使用小写字母,禁止超过32个字符,须见名知意5&a…

set和map去重调用什么方法_你真的了解ES6的Set,WeakSet,Map和WeakMap吗?

之前在学习 ES6 的时候,看到 Set 和 Map,不知道其应用场景有哪些,只觉得很多时候会用在数组去重和数据存储,后来慢慢才领悟到 Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。SetSet 本身是一个构造函数…

c语言整数反转用while函数,7.整数反转(LeetCode)——C语言

根据题目要求,必须是32位有符号整数,数值范围是[-2^31, 2^31-1],换算出来就是-2147483648 —— 2147483647之间。将此范围内的数值反转可能会导致溢出,比如1234567893,反转之后为3987654321,已然超出了以上…

python画饼图存在的问题_Matplotlib 绘制饼图解决文字重叠的方法

在使用Matplotlib 绘制饼图的时候有些时候一些数据的比列太小在饼图呈现的效果不明显 很容易被覆盖,为了解决这个问题以下就是我个人的心得。 【未解决之前呈现的效果】可以看到这个饼状图其他和硕士这2个部分占比很小而且比例相互覆盖,这让人看起来不舒…

antdesign图片点击放大_点击图片放大特效代码,全屏显示,再点击恢复原状【多种方法】...

我们先来看看效果如何,点击图片试一试。图片的实际大小为1920x1080,我们先把宽度限制在300px,点击后图片还原到100%(如果浏览器窗口高度小于图片的真实高度,这样的情况下,图片虽然宽度还原到100%,但是所显示…

c语言中math的库函数,C语言中math.h库中的常用函数

C语言中math.h库中的常用函数 int abs(int i) 返回整型参数i的绝对值double cabs(struct complex znum) 返回复数znum的绝对值double fabs(double x) 返回双精度参数x的绝对值long labs(long n) 返回长整型参数n的绝对值double exp(double x) 返回指数函数e^x的值double frexp(…

python变量类型是动态的_【Python】python动态类型

在python中,省去了变量声明的过程,在引用变量时,往往一个简单的赋值语句就同时完成了,声明变量类型,变量定义和关联的过程,那么python的变量到底是怎样完成定义的呢? 动态类型 python使用动态类…

python bottle部署g_python bottle框架(WEB开发、运维开发)教程 | linux系统运维

教程目录一:python基础二:bottle基础python bottle 框架基础教程:环境部署三:WEB开发教程四:运维开发教程运维开发(1.1):框架、结构介绍运维开发(1.2):前端(ajax)说明运维开发(1.3):…

c语言excel转pdf,基于C语言和Excel软件下光速测量仪测量玻璃折射率.pdf

基于C语言和Excel软件下光速测量仪测量玻璃折射率.pdf基于语言和 软件下光速测量仪测量玻璃折射率 朱承君 王奇峰 芦立娟 张艳春 ( 浙江海洋学院机电学院 浙江 舟山 ) ( 收稿日期 ) 摘要 介绍了用等相位法测玻璃折射率的原理和方法, 并利用了 语言和 在科学计算中的…

python3.7界面_Python3.7+tkinter实现查询界面功能

Tkinter 是 Python 的标准 GUI 库。Python 使用 Tkinter 可以快速的创建 GUI 应用程序。 这篇文章使用tkinter实现一个简单的查询界面 #!/usr/bin/python # -*- coding: UTF-8 -*- from tkinter import * import sqlite3 # 导入消息对话框子模块 import tkinter.messagebox #im…

python中级水平_python 初级/中级/高级/核心

"一等对象":满足条件:1.在运行时创建 2.能赋值给变量或数据结构中的元素 3.能作为参数传递给函数 4.能作为函数的返回结果[ 整数、字符串、字典、"所有函数" ]等都是一等对象"什么是函数"调用:直接使用、不需要类或对象进…

c语言报错spawning 插1,C语言错误····error spawning c1.exe

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼“CL.exe”是VC使用真正的编译器(编译程序),其路径在“VC根目录\VC98\Bin”下面,你可以到相应的路径下找到这个应用程序。因此问题可以按照以下方法解决:打开vc界面 点击VC“TOOLS(工具)”—>…

python中的元类_Python中的元类(metaclass)

提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解。他知道这肯定和自身有关,但仍然觉得不太明白,希望大家可以给出一些实际的例子和代码片段以帮助理解,以及在什么情况下需要进行元编程。 …

方言大全_长沙人亲戚称呼大全!记得收藏以防失传!

长沙人亲戚称呼大全!记得收藏以防失传!首先,看一下中国亲戚称谓图!▼长沙方言对于亲人的称谓自有一套说法!(如有不同,以你自己的叫法为准哦~)▼长辈篇父亲:ya、“爷(ya)老倌”,“爷(…

adb android源码分析,Android Adb 源码解析(base on Android 9.0)

Adb 框架Adb架构Android Adb 一共分为三个部分:adb、adb server、adbd,源码路径:system⁩/⁨core⁩/⁨adb。adb和adb server 是运行在PC端,adb就是大家所熟悉的控制台命令adb,adb server是由adb fork出的一个常驻后台的…

oracle 解锁 账户_oracle用户解锁三种方法

ORA-28000: the account is locked-的解决办法2009-11-11 18:51ORA-28000: the account is locked第一步:使用PL/SQL,登录名为system,数据库名称不变,选择类型的时候把Normal修改为Sysdba;第二步:选择myjob,查看users;第三步&…