• Netty核心概念(5)之Channel


    1.前言

     上一节讲了Netty的第一个关键启动类,启动类所做的一些操作,和服务端的channel固定的handler执行过程,谈到了不管是connect还是bind方法最终都是调用了channel的相关方法,此节开始对channel进行说明。channel设置的概念非常多,而且都很重要,先放个NIO的客户端Channel的类结构图。

    2.主要概念

    2.1 channel

       channel就是直接与操作系统层到交道的数据通道了,可能是java提供的,也可能是通过native方法自己扩展了C++功能的渠道,但是不管哪类,都有一个基础的定义。下面是channel中主要定义的接口方法:

      id():获取该channel的标识

      eventloop():获取该channel注册的线程池

      parent():获取该channel的父channel,NIO没有父channel,一般为null

      config():获取该channel的配置

      isOpen():该channel是否打开状态

      isRegistered():该channel是否注册到线程池中

      isActive():该channel是否可用

      metadata():该channel的元数据

      localAddress():该channel的本地绑定地址端口

      remoteAddress():该channel连接的对端的地址端口

      closeFuture():该channel关闭时触发的future

      isWritable():该channel当前是否可写,只有IO线程会处理可写状态

      bytesBeforeUnwritable():该channel还能写多少字节

      unsafe():获取该channel的unsafe操作对象,对于channel的读写,一般不直接操作channel,而是转交给unsafe对象处理,channel本身通常只做查询状态,获取相关字段内容的操作。

      alloc():获取分配的缓冲区

      read():进行read操作

      write():进行write操作

     上面的方法我们看见了一个不熟悉的unsafe对象,这个也是一个比较重要的概念,理解该类在整个结构所处的位置作用,对于理解框架有较大的帮助。Unsafe被直接定义在Channel接口内部,意味着该接口是与Channel绑定的,上述方法的时候也解释过该类作用。channel本身不直接做相应的工作,交给unsafe方法调用。下图是unsafe的接口定义:

      recvBufAllocHandle():获取处理读取channel数据之后处理的handler

      localAddress():本地地址端口

      remoteAddress():远程地址端口

      register():将channel注册到线程池中

      bind():服务端绑定本地端口

      connect():客户端连接远程端口

      disconnect():断开连接

      close():关闭channel

      closeForcibly():强制关闭

      deregister():移除线程池的注册

      beginRead():准备读取数据

      write():写入数据

      flush():强制刷新

      voidPromise():特殊的promise

      outboundBuffer():获取输出数据的buffer操作类

     看到上面的一系列接口就能够明白,实际操作channel的是unsafe类,但是是直接操作unsafe类吗?比如绑定端口的时候确实调用的是channel.bind方法啊,实际上这里还涉及其它概念,绕了一圈进行操作的。unsafe看名称也应该明白,这个对channel进行操作的类是个线程非安全的类,所以一般通过Netty本身的结构设计,保证线程隔离,才能放心使用。当然如果不自己定义一种IO方式,基本上使用现在Netty封装好的不会有什么问题,如果自己造轮子,这个就要额外注意了。

    2.2 ChannelPipeline

     在前面陆续都提到了这个pipeline,本小节就好好聊聊这个类的作用。先不看接口定义,先关注该类在整个结构所处的位置。打开AbstractChannel,仔细研究一下这个类抽象的channel类,你就会有所收获。channel的主要操作方法大部分都是通过pipeline来完成的,如:bind,connect,close,deregister,flush,read,write等。奇怪吗?并不是我们上面说的由unsafe处理。但是这并不矛盾,unsafe是对最底层最基础的处理,我们会有一系列的业务层需要处理,比如bind时对socket的参数设置交由handler处理,所以channel会将相关操作委托给pipeline处理,pipeline经过一系列操作,最后调用unsafe的相关动作,最终回到channel。pipeline翻译是管道,这里感觉更像流水线操作。

     现在再来看pipeline的基本定义就不会觉得突兀了。

     pipline的方法很多,但是分成两大类:1.注册handler;2.使用handler;截图是使用handler的方法,注册handler的方法很多,这里不进行介绍。ChannelPipeline没有那么多的实现类,基础的就是DefaultChannelPipeline,所以直接对该类的部分方法解析。

     1.先是注册handler的方法究竟干了什么,拿例子中使用的addLast()方法为例:

        public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
            final AbstractChannelHandlerContext newCtx;
            synchronized (this) {
                checkMultiplicity(handler);
    
                newCtx = newContext(group, filterName(name, handler), handler);
    
                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()) {
                    newCtx.setAddPending();
                    executor.execute(new Runnable() {
                        @Override
                        public void run() {
                            callHandlerAdded0(newCtx);
                        }
                    });
                    return this;
                }
            }
            callHandlerAdded0(newCtx);
            return this;
        }
    

     要看懂这段代码还有个内容要注意,tail和head,以及handlerContext(这个是handler相关的概念,这里不进行介绍)。tail和head是pipeline持有的handler头结点和尾结点,看见addLast的时候就应该明白,handler是以链式结构串起来的,在前面也说过,handler是职责链模式,是有先后顺序的。这个方法就是将handler放在职责链尾。中间有个过程,将handler用context包装了,这个不是本节的重点,之后handler章再介绍。

     2.接下来就是pipeline的重点,其是通过什么方式操作channel的,或者说是操作channel的过程是怎样的。

        public final ChannelFuture bind(SocketAddress localAddress) {
            return tail.bind(localAddress);
        }
    

     基本的channel操作都是通过默认的tail完成的,这些操作有bind,connect,close,deregister,flush,read,write。tail是一个handlerContext,这里会涉及一些handlerContext的内容,简略说下吧:在之前pipeline添加handler的时候,生成了context,context构成了链结构,其知道自己的前后handler是哪个。其他的不细说,最终是通过tail的这个handler不断的早它前一个out类型的handler,最终找到head,看HeadContext类你就会明白了,该类获取了channel的unsafe对象,所有操作都由该对象完成,这样整个环节就连上了。由channel生成channelpipeline和unsafe对象,所有操作交给pipeline,pipeline从tail一直搜索到head,最后由head获取channel的unsafe方法,最终进行相关操作。

     还有一类方法这里也进行介绍一下,牵扯到handlerContext职责链的运行过程,不细讲。

        public final ChannelPipeline fireChannelActive() {
            AbstractChannelHandlerContext.invokeChannelActive(head);
            return this;
        }
    

     这个从head开始调用

         public void channelActive(ChannelHandlerContext ctx) throws Exception {
                ctx.fireChannelActive();
    
                readIfIsAutoRead();
            }
    

     然后又调用了自己的fireChannelActive方法,从head往后找,next的in类型的方法,推动了整个职责链的操作了。所以pipeline的fireChannelActive()方法是起始方法,推动职责链的调用。

    3.后记

      channel一共有三个重要的概念:

        1.Channel本身不做事情,将事件都交给ChannelPipeline。

        2.ChannelPipeline本身也不做什么,其主要是控制handler链,由tail查询到head持有unsafe对象,控制channel的连接,读取,写入,提供了由head到tail的直接触发事件链方法fireXXX。

        3.Unsafe是操作channel的最终位置。

      最后附上一个关系图,该图只有一部分,并不完全,不要完全相信:

      

  • 相关阅读:
    【QQ空间转移】C/C++函数的调用约定
    【QQ空间转移】BIG Endian 和 Little Endian模式
    【QQ空间转移】友元函数
    【QQ空间转移】和室友争论所瞎想的
    【QQ空间转移】银行同业拆借
    【QQ空间转移】票据和债券
    js实现给数字加三位一逗号间隔的两种方法
    js获取上个月第一天
    获取所选月份指定时间范围
    PLSQL 11 注册码
  • 原文地址:https://www.cnblogs.com/lighten/p/8950347.html
Copyright © 2020-2023  润新知