Netty学习笔记之ChannelHandler

前言

ChannelHandler是netty中的核心处理部分,我们使用netty的绝大部分代码都写在这部分,所以了解它的一些机制和特性是很有必要的

Channel

Channel接口抽象了底层socket的一些状态属性以及调用方法

针对不同类型的socket提供不同的子类实现。

Channel生命周期

ChannelHandler

ChannelHandler接口里面只定义了三个生命周期方法,我们主要实现它的子接口ChannelInboundHandler和ChannelOutboundHandler,为了便利,框架提供了ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler这三个适配类提供一些默认实现,在使用的时候只需要实现你关注的方法即可

ChannelHandler生命周期方法

ChannelHandler里面定义三个生命周期方法,分别会在当前ChannelHander加入ChannelHandlerContext中,从ChannelHandlerContext中移除,以及ChannelHandler回调方法出现异常时被回调

ChannelInboundHandler

介绍一下这些回调方法被触发的时机

回调方法触发时机
channelRegistered当前channel注册到EventLoop
channelUnregistered当前channel从EventLoop取消注册
channelActive当前channel活跃的时候
channelInactive当前channel不活跃的时候,也就是当前channel到了它生命周期末
channelRead当前channel从远端读取到数据
channelReadCompletechannel read消费完读取的数据的时候被触发
userEventTriggered用户事件触发的时候
channelWritabilityChangedchannel的写状态变化的时候触发

可以注意到每个方法都带了ChannelHandlerContext作为参数,具体作用是,在每个回调事件里面,处理完成之后,使用ChannelHandlerContext的fireChannelXXX方法来传递给下个ChannelHandler,netty的codec模块和业务处理代码分离就用到了这个链路处理

ChannelOutboundHandler

回调方法触发时机
bindbind操作执行前触发
connectconnect 操作执行前触发
disconnectdisconnect 操作执行前触发
closeclose操作执行前触发
deregisterderegister操作执行前触发
readread操作执行前触发
writewrite操作执行前触发
flushflush操作执行前触发

注意到一些回调方法有ChannelPromise这个参数,我们可以调用它的addListener注册监听,当回调方法所对应的操作完成后,会触发这个监听 下面这个代码,会在写操作完成后触发,完成操作包括成功和失败

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    ctx.write(msg,promise);
    System.out.println("out write");
    promise.addListener(new GenericFutureListener<Future<? super Void>>() {
        @Override
        public void operationComplete(Future<? super Void> future) throws Exception {
            if(future.isSuccess()){
                System.out.println("OK");
            }
        }
    });
}
复制代码

ChannelInboundHandler和ChannelOutboundHandler的区别

个人感觉in和out的区别主要在于ChannelInboundHandler的channelRead和channelReadComplete回调和ChannelOutboundHandler的write和flush回调上,ChannelOutboundHandler的channelRead回调负责执行入栈数据的decode逻辑,ChannelOutboundHandler的write负责执行出站数据的encode工作。其他回调方法和具体触发逻辑有关,和in与out无关。

ChannelHandlerContext

每个ChannelHandler通过add方法加入到ChannelPipeline中去的时候,会创建一个对应的ChannelHandlerContext,并且绑定,ChannelPipeline实际维护的是ChannelHandlerContext 的关系 在DefaultChannelPipeline源码中可以看到会保存第一个ChannelHandlerContext以及最后一个ChannelHandlerContext的引用

final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
复制代码

而在AbstractChannelHandlerContext源码中可以看到

volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
复制代码

每个ChannelHandlerContext之间形成双向链表

ChannelPipeline

在Channel创建的时候,会同时创建ChannelPipeline

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

在ChannelPipeline中也会持有Channel的引用

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}
复制代码

ChannelPipeline会维护一个ChannelHandlerContext的双向链表

final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
复制代码

链表的头尾有默认实现

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;
}
复制代码

我们添加的自定义ChannelHandler会插入到head和tail之间,如果是ChannelInboundHandler的回调,根据插入的顺序从左向右进行链式调用,ChannelOutboundHandler则相反

具体关系如下,但是下图没有把默认的head和tail画出来,这两个ChannelHandler做的工作相当重要

上面的整条链式的调用是通过Channel接口的方法直接触发的,如果使用ChannelContextHandler的接口方法间接触发,链路会从ChannelContextHandler对应的ChannelHandler开始,而不是从头或尾开始

HeadContext

HeadContext实现了ChannelOutboundHandler,ChannelInboundHandler这两个接口

class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler
复制代码

因为在头部,所以说HeadContext中关于in和out的回调方法都会触发 关于ChannelInboundHandler,HeadContext的作用是进行一些前置操作,以及把事件传递到下一个ChannelHandlerContext的ChannelInboundHandler中去 看下其中channelRegistered的实现

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    invokeHandlerAddedIfNeeded();
    ctx.fireChannelRegistered();
}
复制代码

从语义上可以看出来在把这个事件传递给下一个ChannelHandler之前会回调ChannelHandler的handlerAdded方法 而有关ChannelOutboundHandler接口的实现,会在链路的最后执行,看下write方法的实现

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    unsafe.write(msg, promise);
}
复制代码

这边的unsafe接口封装了底层Channel的调用,之所以取名为unsafe,是不需要用户手动去调用这些方法。这个和阻塞原语的unsafe不是同一个 也就是说,当我们通过Channel接口执行write之后,会执行ChannelOutboundHandler链式调用,在链尾的HeadContext ,在通过unsafe回到对应Channel做相关调用 从netty Channel接口的实现就能论证这个

public ChannelFuture write(Object msg) {
    return pipeline.write(msg);
}
复制代码

TailContext

TailContext实现了ChannelInboundHandler接口,会在ChannelInboundHandler调用链最后执行,只要是对调用链完成处理的情况进行处理,看下channelRead实现

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    onUnhandledInboundMessage(msg);
}
复制代码

如果我们自定义的最后一个ChannelInboundHandler,也把处理操作交给下一个ChannelHandler,那么就会到TailContext,在TailContext会提供一些默认处理

protected void onUnhandledInboundMessage(Object msg) {
    try {
        logger.debug(
                "Discarded inbound message {} that reached at the tail of the pipeline. " +
                        "Please check your pipeline configuration.", msg);
    } finally {
        ReferenceCountUtil.release(msg);
    }
}
复制代码

比如channelRead中的onUnhandledInboundMessage方法,会把msg资源回收,防止内存泄露

强调一点的是,如果要执行整个链路,必须通过调用Channel方法触发,ChannelHandlerContext引用了ChannelPipeline,所以也能间接操作channel的方法,但是会从当前ChannelHandlerContext绑定的ChannelHandler作为起点开始,而不是ChannelHandlerContext的头和尾 这个特性在不需要调用整个链路的情况下可以使用,可以增加一些效率

上述组件之间的关系

  1. 每个Channel会绑定一个ChannelPipeline,每个ChannelPipeline会持有一个Channel
  2. 每个ChannelHandler对应一个ChannelHandlerContext,ChannelPipeline持有ChannelHandlerContext链表,也就相当于持有ChannelHandler链表
  3. ChannelHandlerContext作为上下文,持有ChannelPipeline和它对应ChannelHandler的引用,持有ChannelPipeline相当于间接持有Channel,同时持有它上/下一个ChannelHandlerContext的引用

示例程序

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        //两个线程池
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        NioEventLoopGroup childEventLoopGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap
                .group(eventLoopGroup,childEventLoopGroup)
                .channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(9999))
                //对serversocketchannel的回调
                .handler(new ChannelInitializer<ServerSocketChannel>() {
                    @Override
                    protected void initChannel(ServerSocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                                .addLast(new TestInboundChannelHandler("server  in "))
                                .addLast(new TestOutboundChannelHandler("server  out "));

                    }
                })
                //对socketchannel的回调
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                                .addLast(new TestInboundChannelHandler("first in "))
                                .addLast(new TestInboundChannelHandler("second in "))
                                .addLast(new EchoChannelHandler())
                                .addLast(new TestOutboundChannelHandler("first out "))
                                .addLast(new TestOutboundChannelHandler("second out "));

                    }
                });
        //等到绑定完成
        ChannelFuture channelFuture = bootstrap.bind().sync();
        //等到serversocketchannel close在退出
        channelFuture.channel().closeFuture().sync();

    }

    static class TestInboundChannelHandler extends ChannelInboundHandlerAdapter{

        private String name;

        public TestInboundChannelHandler(String name) {
            this.name = name;
        }

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"channelRegistered spread");
            ctx.fireChannelRegistered();
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"channelUnregistered not spread");
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"channelActive not spread");
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"channelInactive not spread");
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println(name+"channelRead spread");
            ctx.fireChannelRead(msg);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"channelReadComplete spread");
            ctx.fireChannelReadComplete();
        }
    }

    static class EchoChannelHandler extends ChannelInboundHandlerAdapter{
        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("echo channelRegistered");
            //ctx.writeAndFlush(Unpooled.copiedBuffer("Hello\r\n".getBytes()));
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("echo channelUnregistered");
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("echo channelInactive");
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("echo channelActive");
            ctx.writeAndFlush(Unpooled.copiedBuffer("Hello\r\n".getBytes()));
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("echo channelRead");
            ByteBuf byteBuf = (ByteBuf)msg;
            System.out.println(new String(ByteBufUtil.getBytes(byteBuf)));
            //ctx.write(msg);
            ctx.channel().write(msg);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            System.out.println("echo channelReadComplete");
            ctx.channel().flush();
        }
    }

    static class TestOutboundChannelHandler extends ChannelOutboundHandlerAdapter{

        private String name;

        public TestOutboundChannelHandler(String name) {
            this.name = name;
        }

        @Override
        public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
            //super.bind(ctx,localAddress,promise);
            System.out.println(name+ "bind");
        }

        @Override
        public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
            super.connect(ctx,remoteAddress,localAddress,promise);
            System.out.println(name+"connect");
        }

        @Override
        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
            //super.disconnect(ctx,promise);
            System.out.println(name+"disconnect");
        }

        @Override
        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
            //super.close(ctx,promise);
            System.out.println(name+"close");
        }

        @Override
        public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
            System.out.println(name+"deregister");
        }

        @Override
        public void read(ChannelHandlerContext ctx) throws Exception {
            ctx.read();
            System.out.println(name+"read");
        }

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            //super.write(ctx,msg,promise);
            ctx.write(msg,promise);
            System.out.println(name+"write");
            //promise.setSuccess();
            promise.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    if(future.isSuccess()){
                        System.out.println(name+" listener trigger");
                    }
                }
            });
        }

        @Override
        public void flush(ChannelHandlerContext ctx) throws Exception {
            System.out.println(name+"flush");
            ctx.flush();
        }
    }
}
复制代码

这个demo主要测试了ChannelHandler链的传递以及触发时机

最后

希望大家关注下我的公众号,欢迎朋友们投稿

转载于:https://juejin.im/post/5add778bf265da0ba26697b3

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Exception caught when during method invocation. request:net.risesoft.rpc.itemAdmin.DocumentManager.edit4Position(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) requestId=1771270236171928205 java.lang.reflect.InvocationTargetException: null at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.weibo.api.motan.rpc.DefaultProvider.invoke(DefaultProvider.java:64) at com.weibo.api.motan.rpc.AbstractProvider.call(AbstractProvider.java:52) at com.weibo.api.motan.transport.ProviderMessageRouter.call(ProviderMessageRouter.java:98) at com.weibo.api.motan.transport.ProviderProtectedMessageRouter.call(ProviderProtectedMessageRouter.java:75) at com.weibo.api.motan.transport.ProviderMessageRouter.handle(ProviderMessageRouter.java:93) at com.weibo.api.motan.transport.support.DefaultRpcHeartbeatFactory$HeartMessageHandleWrapper.handle(DefaultRpcHeartbeatFactory.java:98) at com.weibo.api.motan.transport.netty4.NettyChannelHandler.processRequest(NettyChannelHandler.java:155) at com.weibo.api.motan.transport.netty4.NettyChannelHandler.processMessage(NettyChannelHandler.java:133) at com.weibo.api.motan.transport.netty4.NettyChannelHandler.access$000(NettyChannelHandler.java:32) at com.weibo.api.motan.transport.netty4.NettyChannelHandler$1.run(NettyChannelHandler.java:73) at java.util.concurrent.ThreadPoolExecutor.runWorker(Threa是哪里的问题
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值