Netty 源码解析 - 五分钟就能看懂pipeline模型

点击上方“阿拉奇学Java”,选择“置顶或者星标

优质文章第一时间送达!

640?wx_fmt=jpeg

推荐阅读 |  

一、pipeline介绍

1. 什么是pipeline

pipeline 有管道,流水线的意思,最早使用在 Unix 操作系统中,可以让不同功能的程序相互通讯,使软件更加”高内聚,低耦合”,它以一种”链式模型”来串起不同的程序或组件,使它们组成一条直线的工作流。

2. Netty的ChannelPipeline 

ChannelPipeline 是处理或拦截channel的进站事件和出站事件的双向链表,事件在ChannelPipeline中流动和传递,可以增加或删除ChannelHandler来实现对不同业务逻辑的处理。通俗的说,ChannelPipeline是工厂里的流水线,ChannelHandler是流水线上的工人。   ChannelPipeline在创建Channel时会自动创建,每个Channel都拥有自己的ChannelPipeline。

3. Netty I/O事件的处理过程

640?wx_fmt=png

 如图所示,入站事件是由I/O线程被动触发,由入站处理器按自下而上的方向处理,在中途可以被拦截丢弃,出站事件由用户handler主动触发,由出站处理器按自上而下的方向处理。

640?wx_fmt=png

二、ChannelHandlerContext

1. 什么是ChannelHandlerContext  

ChannelHandlerContext是将ChannelHandler和ChannelPipeline关联起来的上下文环境,每添加一个handler都会创建ChannelHandlerContext实例,管理ChannelHandler在ChannelPipeline中的传播流向。

2. ChannelHandlerContext和ChannelPipeline以及ChannelHandler之间的关系   

ChannelPipeline依赖于Channel的创建而自动创建,保存了channel,将所有handler组织起来,相当于工厂的流水线。   ChannelHandler拥有独立功能逻辑,可以注册到多个ChannelPipeline,是不保存channel的,相当于工厂的工人。   ChannelHandlerContext是关联ChannelHandler和ChannelPipeline的上下文环境,保存了ChannelPipeline,控制ChannelHandler在ChannelPipeline中的传播流向,相当于流水线上的小组长。

三、传播Inbound事件

1. Inbound事件有哪些?

640?wx_fmt=png

(1) channelRegistered 注册事件,channel注册到EventLoop上后调用,例如服务岗启动时,pipeline.fireChannelRegistered();  channelUnregistered 注销事件,channel从EventLoop上注销后调用,例如关闭连接成功后,pipeline.fireChannelUnregistered();    channelActive 激活事件,绑定端口成功后调用,pipeline.fireChannelActive();   channelInactive非激活事件,连接关闭后调用,pipeline.fireChannelInactive();    channelRead 读事件,channel有数据时调用,pipeline.fireChannelRead();    channelReadComplete 读完事件,channel读完之后调用,pipeline.fireChannelReadComplete();    channelWritabilityChanged 可写状态变更事件,当一个Channel的可写的状态发生改变的时候执行,可以保证写的操作不要太快,防止OOM,pipeline.fireChannelWritabilityChanged();   userEventTriggered  用户事件触发,例如心跳检测,ctx.fireUserEventTriggered(evt);    exceptionCaught 异常事件说明:我们可以看出,Inbound事件都是由I/O线程触发,用户实现部分关注的事件被动调用。   说明 : 我们可以看出,Inbound事件都是由I/O线程触发,用户实现部分关注的事件被动调用。

2. 添加读事件  

从前面《》和《》我们知道,当有新连接接入时,我们执行注册流程,注册成功后,会调用channelRegistered,我们从这个方法开始。

   public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {	
          initChannel((C) ctx.channel());	
          ctx.pipeline().remove(this);	
          ctx.fireChannelRegistered();	
}

initChannel是在服务启动时配置的参数childHandler重写了父类方法。

private class IOChannelInitialize extends ChannelInitializer<SocketChannel> {	
    @Override	
    protected void initChannel(SocketChannel ch) throws Exception {	
        System.out.println("initChannel");	
        ch.pipeline().addLast(new IdleStateHandler(1000, 0, 0));	
        ch.pipeline().addLast(new IOHandler());	
    }	
}

我们回忆一下,pipeline是在哪里创建的。

protected AbstractChannel(Channel parent) {	
    this.parent = parent;	
    unsafe = newUnsafe();	
    pipeline = new DefaultChannelPipeline(this);	
}

当创建channel时会自动创建pipeline。

public DefaultChannelPipeline(AbstractChannel channel) {	
    if (channel == null) {	
        throw new NullPointerException("channel");	
    }	
    this.channel = channel;	

	
    tail = new TailContext(this);	
    head = new HeadContext(this);	

	
    head.next = tail;	
    tail.prev = head;	
}

在这里会创建两个默认的handler,一个InboundHandler --> TailContext,一个OutboundHandler --> HeadContext。

再看addLast方法。

@Override	
public ChannelPipeline addLast(ChannelHandler... handlers) {	
    return addLast(null, handlers);	
}

在这里生成一个handler名字,生成规则由handler类名加 ”#0”。

  @Override	
public ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {	
    …	
    for (ChannelHandler h: handlers) {	
        if (h == null) {	
            break;	
        }	
        addLast(executor, generateName(h), h);	
    }	
    return this;	
}
@Override	
public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {	
    synchronized (this) {	
        checkDuplicateName(name);	
        AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);	
        addLast0(name, newCtx);	
    }	
    return this;	
}

由于pipeline是线程非安全的,通过加锁来保证并发访问的安全,进行handler名称重复性校验,将handler包装成DefaultChannelHandlerContext,最后再添加到pipeline。

private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {	
    checkMultiplicity(newCtx);	

	
    AbstractChannelHandlerContext prev = tail.prev;	
    newCtx.prev = prev;	
    newCtx.next = tail;	
    prev.next = newCtx;	
    tail.prev = newCtx;	

	
    name2ctx.put(name, newCtx);	

	
    callHandlerAdded(newCtx);	
}

这里分三步    DefaultChannelHandlerContext进行重复性校验,如果DefaultChannelHandlerContext不是可以在多个pipeline中共享的,且已经被添加到pipeline中,则抛出异常。 pipeline中的指针      IdleStateHandler之前HeadContext --> IOChannelInitialize --> TailContext

640?wx_fmt=png

添加IdleStateHandler之后

HeadContext --> IOChannelInitialize --> IdleStateHandler --> TailContext

640?wx_fmt=png

(3)将handler名和DefaultChannelHandlerContext建立映射关系

(4)回调handler添加完成监听事件

最后删除IOChannelInitialize

640?wx_fmt=png

最后事件链上的顺序为:       HeadContext --> IdleStateHandler --> IOHandler --> TailContext

3. pipeline.fireChannelRead()事件解析   

在这里我们选一个比较典型的读事件解析,其他事件流程基本类似

 private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {  	
  …	
  if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {	
        unsafe.read();	
  }	
  …	
}
当boss线程监听到读事件,会调用**unsafe.read()**方法
@Override	
public final void read() {	
  …	
  pipeline.fireChannelRead(byteBuf);	
  …	
}
入站事件从head开始,tail结束。
@Override	
public ChannelPipeline fireChannelRead(Object msg) {	
    head.fireChannelRead(msg);	
    return this;	
}
@Override	
public ChannelHandlerContext fireChannelRead(final Object msg) {	
    if (msg == null) {	
        throw new NullPointerException("msg");	
    }	

	
    final AbstractChannelHandlerContext next = findContextInbound();	
    EventExecutor executor = next.executor();	
    if (executor.inEventLoop()) {	
        next.invokeChannelRead(msg);	
    } else {	
        executor.execute(new OneTimeTask() {	
            @Override	
            public void run() {	
                next.invokeChannelRead(msg);	
            }	
        });	
    }	
    return this;	
}
查找pipeline中下一个Inbound事件。
private AbstractChannelHandlerContext findContextInbound() {	
    AbstractChannelHandlerContext ctx = this;	
    do {	
        ctx = ctx.next;	
    } while (!ctx.inbound);	
    return ctx;	
}

640?wx_fmt=png

HeadContext的下一个Inbound事件是IdleStateHandler。
private void invokeChannelRead(Object msg) {	
    try {	
        ((ChannelInboundHandler) handler()).channelRead(this, msg);	
    } catch (Throwable t) {	
        notifyHandlerException(t);	
    }	
}
@Override	
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {	
    if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {	
        reading = true;	
        firstReaderIdleEvent = firstAllIdleEvent = true;	
    }	
    ctx.fireChannelRead(msg);	
}
 将这个channel读事件标识为true,并传到下一个handler。

640?wx_fmt=png

@Override	
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {	
    super.channelRead(ctx, msg);	
    System.out.println(msg.toString());	
}
这里执行IOHandler重写的channelRead()方法,并调用父类channelRead方法。
@Override	
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {	
    ctx.fireChannelRead(msg);	
}
继续调用事件链上的下一个handler。

640?wx_fmt=png

@Override	
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {	
    try {	
        logger.debug(	
                "Discarded inbound message {} that reached at the tail of the pipeline. " +	
                        "Please check your pipeline configuration.", msg);	
    } finally {	
        ReferenceCountUtil.release(msg);	
    }	
}

这里会调用TailContext的Read方法,释放msg缓存。总结:传播Inbound事件是从HeadContext节点往上传播,一直到TailContext节点结束

四、传播Outbound事件

1.Outbound事件有哪些?

640?wx_fmt=png

(1) bind 事件,绑定端口。   close事件,关闭channel。   connect事件,用于客户端,连接一个远程机器。   disconnect事件,用于客户端,关闭远程连接。    deregister事件,用于客户端,在执行断开连接disconnect操作后调用,将channel从EventLoop中注销。    read事件,用于新接入连接时,注册成功多路复用器上后,修改监听为OP_READ操作位。    write事件,向通道写数据。     flush事件,将通道排队的数据刷新到远程机器上。

2. 解析write事件

  ByteBuf resp = Unpooled.copiedBuffer("hello".getBytes());	
  ctx.channel().write(resp);

我们在项目中像上面这样直接调用write写数据,并不能直接写进channel,而是写到缓冲区,还要调用flush方法才能将数据刷进channel,或者直接调用writeAndFlush。    write事件来解析Outbound流程,其他事件流程类似。

@Override	
public ChannelFuture write(Object msg) {	
    return pipeline.write(msg);	
}

通过上下文绑定的channel直接调用write方法,调用channel相对应的事件链上的handler。

@Override	
public ChannelFuture write(Object msg) {	
    return tail.write(msg);	
}

写事件是从tail向head调用,和读事件刚好相反。

@Override	
public ChannelFuture write(Object msg) {	
    return write(msg, newPromise());	
}
@Override	
public ChannelFuture write(final Object msg, final ChannelPromise promise) {	
  ...	
   write(msg, false, promise);	
  ...	
}
private void write(Object msg, boolean flush, ChannelPromise promise) {	
    AbstractChannelHandlerContext next = findContextOutbound();	
    EventExecutor executor = next.executor();	
    if (executor.inEventLoop()) {	
        next.invokeWrite(msg, promise);	
        if (flush) {	
            next.invokeFlush();	
        }	
  ...	
}	
...	
}
经过多次跳转,获取上一个Ounbound事件链的handler。
private AbstractChannelHandlerContext findContextOutbound() {	
    AbstractChannelHandlerContext ctx = this;	
    do {	
        ctx = ctx.prev;	
    } while (!ctx.outbound);	
    return ctx;	
}

640?wx_fmt=png

IdleStateHandler既是Inbound事件,又是Outbound事件

继续跳转到上一个handler

640?wx_fmt=png

上一个是HeadContext处理。

@Override	
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {	
    unsafe.write(msg, promise);	
}
@Override	
public final void write(Object msg, ChannelPromise promise) {	
    ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;	
    ...	
    outboundBuffer.addMessage(msg, size, promise);	
...	
}

从这里我们看到,最终是把数据丢到了缓冲区,自此netty 的pipeline模型我们解析完毕。

有关inbound事件和outbound事件的传输, 可通过下图进行归纳:

640?wx_fmt=png

陶章好:https://juejin.im/post/5cf522dff265da1bab299967

看到这里啦,说明你对这篇文章感兴趣,帮忙一下或者点击文章右下角在。感谢啦!关注公众号,回复「进群」即可进入无广告技术交流群。同时送上250本电子书+学习视频作为见面礼!

有你想看的 精彩 







------长按二维码关注程序媛------

640?wx_fmt=jpeg

640?wx_fmt=png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值