热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

15ChannelInitializer

文章目录ChannelInitializer一、继承关系二、源码2.1注释2.2源码分析2.3handlerAdded和channelRegistered三、使用示例Channel


文章目录

  • ChannelInitializer
    • 一、继承关系
    • 二、源码
      • 2.1 注释
      • 2.2 源码分析
      • 2.3 handlerAdded和channelRegistered
    • 三、使用示例


ChannelInitializer


  • ChannelInitializer 是一种特殊的 ChannelHandler,它也是一种 ChannelInboundHandler,它提供了在通道注册到 EventLoop 后初始化通道的简单方法;其主要目的是在某个 Channel 注册到 EventLoop 后,对这个 Channel 执行一些初始化操作。在初始化完成之后,ChannelInitializer 会 将自己从 Pipeline 中移除,不会影响后续的操作。

  • 作用:在某个Channel注册到EventLoop后,对这个Channel执行一些初始化操作,初始化操作完成后会将自身从Pipeline中移除。


一、继承关系

在这里插入图片描述


二、源码


2.1 注释


  • 源码注释如下:

/*** 一个特殊的ChannelInboundHandler,提供了一个便捷的方式在Channel注册到EventLoop的时候来初始化这个Channel* A special {@link ChannelInboundHandler} which offers an easy way to initialize a {@link Channel} once it was* registered to its {@link EventLoop}.** 很多时候是在 Bootstrap#handler/ServerBootstrap#handler或者ServerBootstrap#childHandler 方法中使用,* 用于初始化 Channel 的 ChannelPipeline* * Implementations are most often used in the context of {@link Bootstrap#handler(ChannelHandler)} ,* {@link ServerBootstrap#handler(ChannelHandler)} and {@link ServerBootstrap#childHandler(ChannelHandler)} to* setup the {@link ChannelPipeline} of a {@link Channel}.**

* 使用示例如下:* public class MyChannelInitializer extends {@link ChannelInitializer} {*     public void initChannel({@link Channel} channel) {*         channel.pipeline().addLast("myHandler", new MyHandler());*     }* }** {@link ServerBootstrap} bootstrap = ...;* ...* bootstrap.childHandler(new MyChannelInitializer());* ...* 
* 注意到该类是由 @Sharable 标注的,因此实现必须是线程安全的,能够复用* Be aware that this class is marked as {@link Sharable} and so the implementation must be safe to be re-used.*/

2.2 源码分析


  • initChannel 方法是我们需要自行实现的

/*** This method will be called once the {@link Channel} was registered. After the method returns this instance* will be removed from the {@link ChannelPipeline} of the {@link Channel}.*

* Channel被注册到EventLoop的时候initChannel会被调用,ChannelInitializer实现类必须重写该方法。* 并且该方法调用返回之后,ChannelInitializer实例会从ChannelPipeline移除** @param ch the {@link Channel} which was registered.* @throws Exception is thrown if an error occurs. In that case it will be handled by* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close* the {@link Channel}.*/protected abstract void initChannel(C ch) throws Exception;

  • initChannel(ChannelHandlerContext ctx) 初始化,执行handler的添加逻辑

@SuppressWarnings("unchecked")private boolean initChannel(ChannelHandlerContext ctx) throws Exception {// Guard against re-entrance. 解决并发问题,原来没有放进去的话会返回null,原来有就不会放进去,返回旧值if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {try {//1.初始化通道,调用用户自己的实现添加 handlerinitChannel((C) ctx.channel());} catch (Throwable cause) {//2.发生异常时,执行异常处理,记录日志并关闭 ChannelHandlerContext// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).// We do so to prevent multiple calls to initChannel(...).exceptionCaught(ctx, cause);} finally {//3.从 pipeline 移除 ChannelInitializer 自身(这行日志是我自己添加的)logger.info("initChannel移除: " + ctx+ ", 内部的handler是: " + ctx.handler()+ ", handler类型是: " + ctx.handler().getClass()+ ", ctx 类型是: " + ctx.getClass());remove(ctx);}//4.初始化成功return true;}//5.初始化失败return false;}

  • 其他代码

@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {private static final InternalLogger logger &#61; InternalLoggerFactory.getInstance(ChannelInitializer.class);// We use a ConcurrentMap as a ChannelInitializer is usually shared between all Channels in a Bootstrap /// ServerBootstrap. This way we can reduce the memory usage compared to use Attributes./*** 由于 ChannelInitializer 通常可以在所有的 Bootstrap/ServerBootstrap 通道中共享&#xff0c;因此我们用一个 ConcurrentMap* 这种方式相对于使用 Attributes 方式&#xff0c;可以减少内存的使用&#xff0c;相当于不同的通道&#xff0c;对应的 ChannelHandlerContext 不同&#xff0c;由* ConcurrentMap 来解决了并发问题*/private final ConcurrentMap<ChannelHandlerContext, Boolean> initMap &#61; PlatformDependent.newConcurrentHashMap();/*** Handle the {&#64;link Throwable} by logging and closing the {&#64;link Channel}. Sub-classes may override this.*/&#64;Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {if (logger.isWarnEnabled()) {logger.warn("Failed to initialize a channel. Closing: " &#43; ctx.channel(), cause);}ctx.close();}private void remove(ChannelHandlerContext ctx) {try {ChannelPipeline pipeline &#61; ctx.pipeline();//1.从 pipeline 找到 ChannelHandler 对应的 ChannelHandlerContext 节点//(pipeline 中的节点是ChannelHandlerContext&#xff0c;ChannelHandlerContext包装了ChannelHandler)if (pipeline.context(this) !&#61; null) {//2.移除对应的handler(ChannelInitializer也是一种handler&#xff0c;内部会把 handler 包// 装成的ChannelHandlerContext 节点&#xff0c;再删除节点&#xff0c;pipeline内部是链表结构&#xff0c;节点// 是ChannelHandlerContext类型)pipeline.remove(this);}} finally {//3.从initMap移除initMap.remove(ctx);}}
}

2.3 handlerAdded和channelRegistered


  • handlerAdded 内部会判断 Channel 是否已经注册&#xff0c;注册的话会调用 initChannel 来执行自定义的handler 添加逻辑&#xff1b;
  • 在 Channel 注册到 EventLoop 上后&#xff0c;会触发 Channel Registered 事件。那么 ChannelInitializer 的 channelRegistered(ChannelHandlerContext ctx) 方法会得到执行&#xff0c;内部调用 initChannel 方法
  • 二者代码如下&#xff1a;

/*** {&#64;inheritDoc} If override this method ensure you call super!*/&#64;Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {//1.已注册if (ctx.channel().isRegistered()) {//添加该行日志调试logger.info("ChannelInitializer 的 handlerAdded 方法执行... " );// This should always be true with our current DefaultChannelPipeline implementation.// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers// will be added in the expected order.initChannel(ctx);}}&#64;Override&#64;SuppressWarnings("unchecked")public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove// the handler.//添加该行日志调试logger.info("进入 ChannelInitializer 的 channelRegistered 方法... " );// 初始化 Channelif (initChannel(ctx)) {//添加该行日志调试logger.info("ChannelInitializer 的 channelRegistered 方法进入if逻辑执行... " );// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not// miss an event.// 重新触发 Channel Registered 事件ctx.pipeline().fireChannelRegistered();} else {// 继续向下一个节点传播Channel Registered 事件// Called initChannel(...) before which is the expected behavior, so just forward the event.ctx.fireChannelRegistered();}}

  • 换言之&#xff0c;用户自定义的 ChannelInitializer 初始化逻辑&#xff0c;看会被这两个方法调用执行&#xff0c;结合添加了日志后调试情况和查资料来看&#xff0c;几乎都是 handlerAdded 方法调用的&#xff0c;不知道什么情况下会由 channelRegistered 调用执行 initChannel(C ch) &#xff1b;
  • 具体调用逻辑&#xff0c;按照调用栈可以回到 AbstractChannel.AbstractUnsafe#register0 方法&#xff0c;如下&#xff1a;

private void register0(ChannelPromise promise) {// ... 其他逻辑pipeline.invokeHandlerAddedIfNeeded();//7.回调通知promise执行成功safeSetSuccess(promise);//8.触发通知已注册事件pipeline.fireChannelRegistered();// ... 其他逻辑}

三、使用示例


  • ChannelInitializer的 initChannel 抽象方法在 Channel 被注册到 EventLoop 的时候会被调用&#xff0c;ChannelInitializer 实现类必须重写该方法。

  • 如下是使用代码

//启动器中设置 ChannelInitializer.childHandler(new MyChannelInitializer());//自定义的ChannelInitializerpublic class MyChannelInitializer extends ChannelInitializer {&#64;Overrideprotected void initChannel(Channel ch) throws Exception {System.out.println("MyChannelInitializer begin ... ");ch.pipeline().addLast("logging",new LoggingHandler(LogLevel.INFO));ch.pipeline().addLast(new EchoServerHandler());}
}

  • 前面提到该方法调用是在 Channel 被注册到 EventLoop 的时候调用&#xff0c;因此运行服务端之后&#xff0c;启动客户端&#xff0c;连接成功之后就会触发方法的调用&#xff0c;下面的代码打印示例&#xff1a;

//server端日志Connected to the target VM, address: &#39;127.0.0.1:65446&#39;, transport: &#39;socket&#39;
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/intellif/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/intellif/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
Thread[nioEventLoopGroup-2-1,10,main]: register
Thread[nioEventLoopGroup-2-1,10,main]: user handler
[nioEventLoopGroup-2-1] INFO io.netty.channel.ChannelInitializer - ChannelInitializer 的 handlerAdded 方法执行...
[nioEventLoopGroup-2-1] INFO io.netty.channel.ChannelInitializer - initChannel移除: ChannelHandlerContext(ServerBootstrap$1#0, [id: 0x024ee434]), 内部的handler是: io.netty.bootstrap.ServerBootstrap$1&#64;5a308693, handler类型是: class io.netty.bootstrap.ServerBootstrap$1, ctx 类型是: class io.netty.channel.DefaultChannelHandlerContext
Thread[nioEventLoopGroup-2-1,10,main]: PendingRegistrationPromise
Thread[nioEventLoopGroup-2-1,10,main]: ServerBootstrapAcceptor
Thread[nioEventLoopGroup-2-1,10,main]: bind[nioEventLoopGroup-2-2] INFO io.netty.channel.ChannelInitializer - ChannelInitializer 的 handlerAdded 方法执行...
Thread[nioEventLoopGroup-2-2,10,main]: register //这里客户端连接成功
MyChannelInitializer begin ... // MyChannelInitializer 执行&#xff0c;后面会移除 MyChannelInitializer
EchoServerHandler 构造方法执行 ... //添加自定义的handler
[nioEventLoopGroup-2-2] INFO io.netty.channel.ChannelInitializer - initChannel移除: ChannelHandlerContext(MyChannelInitializer#0, [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196]), 内部的handler是: com.intellif.channelinitializer.MyChannelInitializer&#64;332f2ee9, handler类型是: class
com.intellif.channelinitializer.MyChannelInitializer, ctx 类型是: class io.netty.channel.DefaultChannelHandlerContext
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] REGISTERED
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] ACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] READ: 12B&#43;-------------------------------------------------&#43;| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
&#43;--------&#43;-------------------------------------------------&#43;----------------&#43;
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21 |Netty rocks! |
&#43;--------&#43;-------------------------------------------------&#43;----------------&#43;
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] WRITE: 12B&#43;-------------------------------------------------&#43;| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
&#43;--------&#43;-------------------------------------------------&#43;----------------&#43;
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21 |Netty rocks! |
&#43;--------&#43;-------------------------------------------------&#43;----------------&#43;
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] READ COMPLETE
Server received: Netty rocks!
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] WRITE: 0B
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] FLUSH
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 - R:/127.0.0.1:49196] CLOSE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 ! R:/127.0.0.1:49196] INACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x38a1d03f, L:/127.0.0.1:12345 ! R:/127.0.0.1:49196] UNREGISTERED

  • 本文主要关于 ChannelInitializer 的用法&#xff0c;主要是通过重写 initChannel 函数来添加需要的 handler 处理器&#xff0c;当当前的 channel 被注册到 Eventloop 之后该方法会被调用&#xff1b;

推荐阅读
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
author-avatar
狗且偷生
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有