博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Netty框架学习之路(四)—— Channel及相关概念
阅读量:4223 次
发布时间:2019-05-26

本文共 9394 字,大约阅读时间需要 31 分钟。

在Netty框架中,Channel是其中之一的核心概念,是Netty网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能。本文我们来详细地分析 Netty 中的 Channel以及跟Channel相关的其他概念,包括ChannelPipeline、ChannelHandlerContext、ChannelHandler等。

Channel的抽象类AbstractChannel有一如下所示的构造方法:

protected AbstractChannel(Channel parent) {        this.parent = parent;        id = newId();        unsafe = newUnsafe();        pipeline = newChannelPipeline();    }

AbstractChannel内部有一个pipeline属性,Netty在对Channel进行初始化的时候将该属性初始化为DefaultChannelPipeline的实例,上述代码说明了每个Channel都有一个ChannelPipeline与之相关联。

跟踪代码,进入newChannelPipeline的方法体内:

protected DefaultChannelPipeline newChannelPipeline() {        return new DefaultChannelPipeline(this);    }    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;    }

上述代码,敏感的读者便能觉察到这是一个新建链表节点,并建立双向循环列表的过程。

这里出现了两个新的类型——TailContext和HeadContext,先来看一下两者的类图:

这里写图片描述
这里写图片描述

TailContext和HeadContext既实现了ChannelHandler接口,又实现了 ChannelHandlerContext接口, 因此可以说head和tail即是一个ChannelHandler, 又是一个 ChannelHandlerContext。这么设计有什么用呢?让我们先来看一下ChannelHandlerContext概念。

HeadContext的构造方法调用了父类AbstractChannelHandlerContext的构造器, 并传入参数inbound = false,outbound = true。

TailContext的构造器与HeadContext相反, 它调用了父类 AbstractChannelHandlerContext 的构造器, 并传入参数 inbound = true,outbound = false。

那么inbound和outbound这两个属性究竟代表什么呢?

其实inbound和outbound分别用于标识 Context 所对应的handler的类型, 在Netty中事件可以分为Inbound和Outbound事件,在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)            | *  +-------------------------------------------------------------------+ *

从上图可以看出, inbound事件和outbound事件的流向是不一样的,inbound事件的流行是从下至上, 而outbound刚好相反,是从上到下。同时注释中列出了引发inbound事件和outbound事件传播的方法:

Inbound event propagation methods:    ChannelHandlerContext.fireChannelRegistered()    ChannelHandlerContext.fireChannelActive()    ChannelHandlerContext.fireChannelRead(Object)    ChannelHandlerContext.fireChannelReadComplete()    ChannelHandlerContext.fireExceptionCaught(Throwable)    ChannelHandlerContext.fireUserEventTriggered(Object)    ChannelHandlerContext.fireChannelWritabilityChanged()    ChannelHandlerContext.fireChannelInactive()    ChannelHandlerContext.fireChannelUnregistered()Outbound event propagation methods:    ChannelHandlerContext.bind(SocketAddress, ChannelPromise)    ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)    ChannelHandlerContext.write(Object, ChannelPromise)    ChannelHandlerContext.flush()    ChannelHandlerContext.read()    ChannelHandlerContext.disconnect(ChannelPromise)    ChannelHandlerContext.close(ChannelPromise)    ChannelHandlerContext.deregister(ChannelPromise)

仔细分析,我们可以发现Inbound事件都是通知事件,而Outbound事件都是请求事件。

了解ChannelHandlerContext的两个重要属性的含义之后,我们回到其类型本身。我们之前的客户端程序有如下代码:

Bootstrap b = new Bootstrap();            b.group(group).channel(NioSocketChannel.class)                    .option(ChannelOption.TCP_NODELAY, true)                    .handler(new ChannelInitializer
() { @Override public void initChannel(SocketChannel ch) throws Exception{ ch.pipeline().addLast(new TimeClientHandler()); } });

通常来说, 我们在初始化 Bootstrap, 会添加我们自定义的 ChannelHandler,上面代码在调用handler方法时, 传入了ChannelInitializer对象, 它提供了一个 initChannel 方法供我们初始化ChannelHandler,让我们来看一下这个初始化过程。

在Bootstrap的init方法中将ChannelInitializer添加到ChannelPipeline中:

void init(Channel channel) throws Exception {        ChannelPipeline p = channel.pipeline();        //handler方法返回的就是之前设置的ChannelInitializer对象        p.addLast(config.handler());        ……    }

进入ChannelPipeline的addLast方法,我们来看一下添加过程:

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {        final AbstractChannelHandlerContext newCtx;        synchronized (this) {            checkDuplicateName(name); // 检查此 handler 是否有重复的名字            newCtx = new DefaultChannelHandlerContext(this, group, name, handler);            addLast0(name, newCtx);        }        return this;    }    private void addLast0(AbstractChannelHandlerContext newCtx) {        AbstractChannelHandlerContext prev = tail.prev;        newCtx.prev = prev;        newCtx.next = tail;        prev.next = newCtx;        tail.prev = newCtx;    }

此时,ChannelPipeline的结构如下图所示:

这里写图片描述

当Channel成功注册到一个eventLoop的Selector中, 并且将当前Channel作为attachment时,便产生了一个Inbound事件触发了ChannelHandlerContext.fireChannelRegistered()方法。进入该方法:

public ChannelHandlerContext fireChannelRegistered() {        invokeChannelRegistered(findContextInbound());        return this;    }    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {        EventExecutor executor = next.executor();        if (executor.inEventLoop()) {            next.invokeChannelRegistered();        } else {            executor.execute(new Runnable() {                @Override                public void run() {                    next.invokeChannelRegistered();                }            });        }    }    private AbstractChannelHandlerContext findContextInbound() {        AbstractChannelHandlerContext ctx = this;        do {            ctx = ctx.next;        } while (!ctx.inbound);        return ctx;    }

findContextInbound方法用于从当前context开始查找下一个inbound属性为true的context,而Inbound事件始于head,同时ChannelInitializer的inbound属性为true,因此返回之前设置的ChannelInitializer对象。再进入invokeChannelRegistered方法:

private void invokeChannelRegistered() {        if (invokeHandler()) {            try {                //此处handler方法返回的是Bootstrap中设置ChannelInitializer对象                ((ChannelInboundHandler) handler()).channelRegistered(this);            } catch (Throwable t) {                notifyHandlerException(t);            }        } else {            fireChannelRegistered();        }    }    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {        if (initChannel(ctx)) {            //基于责任链模式,事件继续向后传递            ctx.pipeline().fireChannelRegistered();        } else {            ctx.fireChannelRegistered();        }    }    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) {             try {                //此处调用的initChannel方法为ChannelInitializer对象的initChannel方法                initChannel((C) ctx.channel());            } catch (Throwable cause) {            } finally {                //功成身退,将ChannelInitializer对象从pipeline中移除                remove(ctx);            }            return true;        }        return false;    }

经过上述步骤之后,channelPipeline最终的结构如下图所示:

这里写图片描述

好了, 到了这里, 我们的自定义ChannelHandler的添加过程 也分析的查不多了。

你可能感兴趣的文章
BFS——求矩阵中“块”的个数
查看>>
BFS——走迷宫的最小步数
查看>>
并查集——好朋友
查看>>
关键路径
查看>>
Web前端学习笔记——JavaScript之事件详解
查看>>
Web前端学习笔记——JavaScript之事件、创建元素、节点操作
查看>>
Web前端学习笔记——JavaScript之正则表达式、伪数组、垃圾回收
查看>>
Web前端学习笔记——JavaScript 之继承、函数进阶
查看>>
Web前端学习笔记——JavaScript之面向对象游戏案例:贪吃蛇
查看>>
不做单元测试?小心得不偿失!嵌入式系统单元测试工具,自动生成测试用例
查看>>
一种实用的联网汽车无线攻击方法及车载安全协议
查看>>
光靠欺骗检测是不够的:对抗多目标跟踪的攻击
查看>>
基于微区块链的V2X地理动态入侵检测
查看>>
面向V2C场景的ADAS数字孪生模型构建方法
查看>>
Comma2k19数据集使用
查看>>
面向自动驾驶车辆验证的抽象仿真场景生成
查看>>
一种应用于GPS反欺骗的基于MLE的RAIM改进方法
查看>>
自动驾驶汽车GPS系统数字孪生建模(一)
查看>>
自动驾驶汽车GPS系统数字孪生建模(二)
查看>>
CUDA 学习(五)、线程块
查看>>