上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析;

一、ChannelHandler的添加

下面是Netty官方的一段demo源码,可以看到在服务端初始化时执行了向ChannelPipeline中添加自定义channelHandler的操作。

            ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
// p.addLast(new LoggingHandler(LogLevel.INFO));
// 向ChannelPipeline中添加自定义channelHandler
p.addLast(serverHandler);
}
});

我们可以看到上面的代码中调用ChannelPipeline的addLast方法实现了channelHandler的添加,下面我们就分析下addLast方法的具体源码实现

首先看下addLast方方法的具体源码实现

    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//判断handler是否被重复添加
checkMultiplicity(handler); //创建ChannelHandlerContext节点 filterName检查名称是否重复
newCtx = newContext(group, filterName(name, handler), handler); //双向链表中增加ChannelHandlerContext
addLast0(newCtx); // If the registered is false it means that the channel was not registered on an eventLoop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
} EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {//判断是否在同一线程中
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}

分析addLast方法代码可以看到,ChannelHandler的添加基本可以分为四步

1、验证ChannelHandler是否重复添加

我们看下checkMultiplicity方法的具体实现

    private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {//如果该handler非共享且已经被添加
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;//添加过之后,修改标识
}
}

2、创建一个HandlerContext对象

我们之前说过netty会把一个channelhandler封装成一个ChannelHandlerContext对象,如下面代码所示
newCtx = newContext(group, filterName(name, handler), handler);
封装对象时,我们可以给要添加的channelhandler起一个名字,filterName方法可以判断该handler的命名是否重复
    private String filterName(String name, ChannelHandler handler) {
if (name == null) {
return generateName(handler);//返回一个默认名称
}
checkDuplicateName(name);
return name;
}
checkDuplicateName方法会遍历链表中节点如果查询到有重复的name则会抛出异常
    private void checkDuplicateName(String name) {
if (context0(name) != null) { //遍历节点,查找是否有重复name
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}
    private AbstractChannelHandlerContext context0(String name) {
AbstractChannelHandlerContext context = head.next;
while (context != tail) {
if (context.name().equals(name)) {
return context;
}
context = context.next;
}
return null;
}

3、向链表中添加添加context

前面进行了一系列判断后,通过addLast0方法我们把ChannelHandlerContext添加到pipeline中的双向链表中

    //相当于在tail节点前面插入一个节点,也就是addLast
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;//拿到tail节点的前置节点
newCtx.prev = prev;//把当前context的前置节点置为 prev
newCtx.next = tail;//把当前context的后置节点置为tail
prev.next = newCtx;//把prev节点的后置节点置为context
tail.prev = newCtx;//把tail节点的前置节点置为context
}

addLast0内部实现很简单,就是在tail节点前面插入一个节点,也就是把该ChannelHandlerContext放在链表的最后。

4、调用回调方法,通知添加成功

这一步就是当ChannelHandler添加到Pipeline中时调用,通过callHandlerAdded()回调方法通知ChannelHandler添加成功,执行handlerAdded()方法;

首先判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程,是同一线程直接执行callHandlerAdded0方法,我们看下方法具体实现

    private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.callHandlerAdded();//调用callHandlerAdded回调方法
} catch (Throwable t) {
boolean removed = false;
try {
remove0(ctx);//如果出现异常的话,把该ctx删除
ctx.callHandlerRemoved();//调用callHandlerRemoved回调方法
removed = true;
} catch (Throwable t2) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to remove a handler: " + ctx.name(), t2);
}
} if (removed) {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() +
".handlerAdded() has thrown an exception; removed.", t));
} else {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() +
".handlerAdded() has thrown an exception; also failed to remove.", t));
}
}
}
    final void callHandlerAdded() throws Exception {
// We must call setAddComplete before calling handlerAdded. Otherwise if the handlerAdded method generates
// any pipeline events ctx.handler() will miss them because the state will not allow it.
if (setAddComplete()) {//在添加handler之前,保证状态为可添加状态
handler().handlerAdded(this);
}
}

通过上面代码实现,我们就可以通过重写ChannelHandler的handlerAdded方法,执行一些当ChannelHandler添加到Pipeline中时需要触发的功能逻辑。

二、ChannelHandler的删除

ChannelHandler的删除主要是通过ChannelPipeline的remove方法来实现的

我们先看下remove方法源码具体实现

    @Override
public final ChannelPipeline remove(ChannelHandler handler) {
remove(getContextOrDie(handler));//删除handler
return this;
}
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
//不能删除头节点和尾节点
assert ctx != head && ctx != tail; //加锁,保证线程安全
synchronized (this) {
remove0(ctx);//在链表中删除context对象 // If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we remove the context from the pipeline and add a task that will call
// ChannelHandler.handlerRemoved(...) once the channel is registered.
//这里主要判断下该pipline对应channel是否已经注册到eventloop上
if (!registered) {
callHandlerCallbackLater(ctx, false);
return ctx;
} EventExecutor executor = ctx.executor();
if (!executor.inEventLoop()) {//判断是否在同一线程中
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerRemoved0(ctx);
}
});
return ctx;
}
}
//调用回调方法,通知handler已删除
callHandlerRemoved0(ctx);
return ctx;
}

删除操作整个流程与添加类似

1、获取ChannelHandlerContext对象

    //根据传入的handler拿到其包装的ChannelHandlerContext对象
private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
//根据context方法从链表中获取该handler的ChannelHandlerContext对象
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
if (ctx == null) {
throw new NoSuchElementException(handler.getClass().getName());
} else {
return ctx;
}
}
    @Override
public final ChannelHandlerContext context(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
} AbstractChannelHandlerContext ctx = head.next;
//遍历链表拿到该handler封装的ChannelHandlerContext对象
for (;;) { if (ctx == null) {
return null;
} if (ctx.handler() == handler) {
return ctx;
} ctx = ctx.next;
}
}

2、判断是否是首尾节点

首先判断删除的节点既不是头节点也不是尾节点

   //不能删除头节点和尾节点
assert ctx != head && ctx != tail;

3、执行删除操作

然后通过remove0方法删除指定Context节点

    private static void remove0(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;//获取当前节点的前置节点
AbstractChannelHandlerContext next = ctx.next;//获取当前节点的后置节点
prev.next = next;//把prev后置节点设置为next
next.prev = prev;//把next前置节点设置为prev
}

4、调用回调方法,通知删除成功

同样会判断当前线程与eventloop线程是否一致,不一致的话封装成task提交给eventloop线程

 EventExecutor executor = ctx.executor();
if (!executor.inEventLoop()) {//判断是否在同一线程中
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerRemoved0(ctx);
}
});
return ctx;
}
 //调用回调方法,通知handler已删除
callHandlerRemoved0(ctx);

调用callHandlerRemoved()回调方法,通知触发handlerRemoved删除成功。

    private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
// Notify the complete removal.
try {
ctx.callHandlerRemoved();//调用ctx中的回调方法
} catch (Throwable t) {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
}
}
    final void callHandlerRemoved() throws Exception {
try {
// Only call handlerRemoved(...) if we called handlerAdded(...) before.
if (handlerState == ADD_COMPLETE) {
handler().handlerRemoved(this);//通知handler删除成功事件
}
} finally {
// Mark the handler as removed in any case.
setRemoved();
}
}

三、总结

通过上面的内容,我们梳理了channelHandler被封装成ChannelHandlerContext对象后,基于ChannelPipeline的双向链表的添加和删除操作,整个流程还是比较简单清晰的。其中如有不足与不正确的地方还望指出与海涵

关注微信公众号,查看更多技术文章。

Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除的更多相关文章

  1. 【Netty源码分析】ChannelPipeline(二)

    在上一篇博客[Netty源码学习]ChannelPipeline(一)中我们只是大体介绍了ChannelPipeline相关的知识,其实介绍的并不详细,接下来我们详细介绍一下ChannelPipeli ...

  2. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  3. Netty源码分析之ChannelPipeline(一)—ChannelPipeline的构造与初始化

    Netty中ChannelPipeline实际上类似与一条数据管道,负责传递Channel中读取的消息,它本质上是基于责任链模式的设计与实现,无论是IO事件的拦截器,还是用户自定义的ChannelHa ...

  4. Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析

    什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...

  5. Netty源码分析之NioEventLoop(二)—NioEventLoop的启动

    上篇文章中我们对Netty中NioEventLoop创建流程与源码进行了跟踪分析.本篇文章中我们接着分析NioEventLoop的启动流程: Netty中会在服务端启动和新连接接入时通过chooser ...

  6. Netty源码分析之ChannelPipeline—入站事件的传播

    之前的文章中我们说过ChannelPipeline作为Netty中的数据管道,负责传递Channel中消息的事件传播,事件的传播分为入站和出站两个方向,分别通知ChannelInboundHandle ...

  7. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

  8. Netty源码分析之ChannelPipeline—出站事件的传播

    上篇文章中我们梳理了ChannelPipeline中入站事件的传播,这篇文章中我们看下出站事件的传播,也就是ChannelOutboundHandler接口的实现. 1.出站事件的传播示例 我们对上篇 ...

  9. Netty源码分析之ChannelPipeline—异常事件的传播

    ChannelHandler中异常的获取与处理是通过继承重写exceptionCaught方法来实现的,本篇文章我们对ChannelPipeline中exceptionCaught异常事件的传播进行梳 ...

随机推荐

  1. 干货 Elasticsearch 知识点整理二

    目录 root object mate-field 元数据字段 mapping-parameters 动态mapping(dynamic mapping) 核心的数据类型 精确匹配与全文检索 精确匹配 ...

  2. 学习笔记31_ORM框架ModelFirst设计数据库

    ModelFirst就是先设计实体数据类型,然后根据设计的数据类型,生成数据库表 1.新建项--ADO.NET实体数据模型--空数据模型--进入模型设计器(点击xxx.edmx文件也能进入设计器). ...

  3. 从壹开始【NetCore3.0】 46 ║ 授权认证:自定义返回格式

    前言 哈喽大家好,马上就要年末了,距离新的一年,只有50天了,春节是75天. 在这个时节内,天气逐渐变凉,但是大家的心肯定很热吧,因为发生了两件大事: 1.双十一买买买,在这个让人激动又纠结的一天,大 ...

  4. Vue项目中执行npm run dev 不报错也不显示点击的地址链接

    问题描述: 输入npm run dev 没有报错也没有显示可以点击的地址链接,如下图: 解决方法: 具体配置: autoOpenBrowser默认为false,改为true.重新 npm run de ...

  5. 主席树学习笔记(静态区间第k大)

    题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出 ...

  6. 「BZOJ1576」[Usaco2009 Jan] 安全路经Travel------------------------P2934 [USACO09JAN]安全出行Safe Travel

    原题地址 题目描述 Gremlins have infested the farm. These nasty, ugly fairy-like creatures thwart the cows as ...

  7. 面试总问的jvm调优到底是要干什么?

    1. 压力测试的理解,xxx的性能10w/s,对你有意义么? 没有那家卖瓜的会说自己家的不甜,同样,没有哪个开源项目愿意告诉你在对它条件最苛刻的时候压力情况是多少,一般官网号称给你看的性能指标都是在最 ...

  8. SpringBoot基本配置详解

    SpringBoot项目有一些基本的配置,比如启动图案(banner),比如默认配置文件application.properties,以及相关的默认配置项. 示例项目代码在:https://githu ...

  9. Maven系列第8篇:你的maven项目构建太慢了,我实在看不下去,带你一起磨刀!!多数使用maven的人都经常想要的一种功能,但是大多数人都不知道如何使用!!!

    maven系列目标:从入门开始开始掌握一个高级开发所需要的maven技能. 这是maven系列第8篇. 整个maven系列的内容前后是有依赖的,如果之前没有接触过maven,建议从第一篇看起,本文尾部 ...

  10. 力扣(LeetCode)验证回文字符串II 个人题解

    给定一个非空字符串 s,最多删除一个字符.判断是否能成为回文字符串. 示例 1: 输入: "aba" 输出: True 示例 2: 输入: "abca" 输出: ...