学习笔记 1.高性能编程 2.2.2 责任链设计模式

设计模式——责任链模式:责任链模式(Chain of Responsibility Pattern)为请求创建了一个处理对象的链。发起请求和具体处理请求的过程进行解耦:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递。

实现责任链模式需要4个要素:处理器抽象类,具体的处理器实现类,保存处理器信息,处理执行

那么在netty中又是如何使用责任链的呢?首先所有处理器的信息保存在Pipeline管道,创建channel的时候会自动创建资格专有的pipeline入站事件和出站操作会调用pipeline上的处理器

入站事件和出站事件

入站事件通常是指I/O线程生成了入站数据。(通俗理解:从socket底层自己往上冒上来的事件都是入站)比如EventLoop收到selector的OP_READ事件,入站处理器调用SocketChannel.read(ByteBuffer),接收到数据后,这将导致通道的ChannelPipeline中包含的下一个中的channelRead方法被调用。

出站事件:经常指I/O线程执行实际的输出操作。(通俗理解:想主动往socket底层操作的事件都是出站事件)。比如bind方法用意是请求server socket绑定到给定的SocketAddress,这将导致通道的ChannelPipeline中包含的下一个出站处理器中的bind方法被调用。

在netty中有很多事件的定义:

而Pipeline中的Handler是什么呢?

ChannelHeadler:用于处理I/O事件或者拦截I/O操作,并转发到ChannelPipeline中下一个处理器。这个顶级接口定义功能很弱,实际使用时会去实现下面两大子接口:处理入站I/O事件的ChannelInboundHandler、处理出站I/O操作的ChannelOutboundHandler

适配器类:为了方便开发,避免所有handler去实现一遍接口方法,Netty提供了简单的实现类:

  1. ChannelInboundHandlerAdapter处理入站I/O事件
  2. ChannelOutboundHandlerAdapter处理出站I/O事件
  3. ChannelDuplexHandler支持同时处理入站和出站事件

ChannelHandlerContext:实际存储在Pipeline中的并非是ChannelHandler,而是上下文对象。将Handler包裹在上下文对象中,通过上下文对象与它所属的ChannelPipeline交互,向上或向下传递事件或者修改pipeline都是通过上下文对象。

那么如何维护Pipeline中的handler呢?ChannelPipeline是线程安全的,ChannelHandler可以在任何时候添加或者删除。例如你可以在即将交换敏感信息时插入加密处理程序,并在交换后删除它。一般操作,初始化的时候增加进去,较少删除。下面是Pipeline中管理的API

通道创建时会会构建一个Pipeline,同时里面会保留链的头和尾(HeadContext-TailContext),跟着源码中注册方法register(),首先从bind()方法开始如何绑定端口,初始化的事件会进入register()方法,首先创建通道,

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();// Tony: 1.创建/初始化ServerSocketChannel对象,并注册到Selector
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
        // Tony: 等注册完成之后,再绑定端口。 防止端口开放了,却不能处理请求
        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);// Tony: 实际操作绑定端口的代码
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();//2.创建通道
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // Tony: (一开始初始化的group)MultithreadEventLoopGroup里面选择一个eventLoop进行绑定
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        // If we are here and the promise is not failed, it's one of the following cases:
        // 1) If we attempted registration from the event loop, the registration has been completed at this point.
        //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
        // 2) If we attempted registration from the other thread, the registration request has been successfully
        //    added to the event loop's task queue for later execution.
        //    i.e. It's safe to attempt bind() or connect() now:
        //         because bind() or connect() will be executed *after* the scheduled registration task is executed
        //         because register(), bind(), and connect() are all bound to the same thread.

        return regFuture;
    }

 

//3.初始化通道的参数,如网络参数,自定义属性
void init(Channel channel) throws Exception {
        Map<ChannelOption<?>, Object> options = this.options0();
        synchronized(options) {
            setChannelOptions(channel, options, logger);
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs0();
        synchronized(attrs) {
            Iterator var5 = attrs.entrySet().iterator();

            while(true) {
                if (!var5.hasNext()) {
                    break;
                }

                Entry<AttributeKey<?>, Object> e = (Entry)var5.next();
                AttributeKey<Object> key = (AttributeKey)e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }
        //4.获取到ChannelPipeline对象
        ChannelPipeline p = channel.pipeline();
        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry[] currentChildOptions;
        synchronized(this.childOptions) {
            currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(0));
        }

        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
            currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(0));
        }
        //5.ChannelInitializer是一个特殊的handler,一般就是在registered之后,
        //执行一次,然后销毁。用于初始化channel,此时Pipeline中只有头和尾,说明头和尾
        //是Pipeline是创建时自带的。此时加在最后,最后的意思不是链表尾部,链表的尾部一定
        //是Tail,此时添加了一个ChannelInitializer
        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            //触发ChannelInitializer时,收到注册成功的事件后,就会执行initChannel方法
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }

从继承ChannelInboundHandlerAdapter可以看到这个类是处理入站事件的处理器,它其中有一个channelRegistered()方法,当Netty底层散发出一个regist事件的时候回将其触发调用,因为regist是一个入站事件。但是如果handlerAdded()方法被执行了,这个方法就不会被调用,因为initChannel被调用之后,这个handler就会被移除,也就是初始化完成后这个handler会被移除,映照了pipeline上的handler是可以动态的增加和删除,查看源码handlerAdded()方法会先判断这个通道是否已经注册,注册完成后,就会对通道进行初始化

@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    private static final InternalLogger logger = 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.
    private final ConcurrentMap<ChannelHandlerContext, Boolean> initMap = PlatformDependent.newConcurrentHashMap();

    /**
     * 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}.
     *
     * @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;

    @Override
    @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.
        if (initChannel(ctx)) {// Tony: 收到注册成功的事件,先执行initChannel(ctx),在执行方法重载的initChannel(ch)
            // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
            // miss an event.
            ctx.pipeline().fireChannelRegistered();
        } else {
            // Called initChannel(...) before which is the expected behavior, so just forward the event.
            ctx.fireChannelRegistered();
        }
    }

    /**
     * Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
        }
        ctx.close();
    }

    /**
     * {@inheritDoc} If override this method ensure you call super!
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 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);
        }
    }

    @SuppressWarnings("unchecked")
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {// Tony: 这个init方法一般就是创建channel时,实现的那个initchannel方法
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
                // We do so to prevent multiple calls to initChannel(...).
                exceptionCaught(ctx, cause);
            } finally {// Tony: ChannelInitializer执行结束之后,会把自己从pipeline中删除掉,避免重复初始化
                remove(ctx);
            }
            return true;
        }
        return false;
    }

    private void remove(ChannelHandlerContext ctx) {
        try {
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
        } finally {
            initMap.remove(ctx);
        }
    }
}

初始化完成后,此时pipeline会有三个handler,除了头和尾还有一个ServerBootstrap匿名实现类用来初始化,从断点中可以清晰的看出

接下来会调用register注册方法,会在group中选取一个EventLoop然后调用其register,实际在调用register方法就是再去调用channel的Unsafe().register()方法,也就是调用AbstractChannel对象的register方法,在这个方法中首先拿到EventLoop,eventloop默认是不执行的,只有当有任务提交时才执行,所以此时理论上eventloop没有执行,所以会以一个任务提交的方式执行,调用EventLoop的execute()方法,这个方法首先判断方法的调用者是不是EventLoop同一个线程,并将其增加到队列中,不是同一个线程则调用启动方法吗,启动完成后,因为EventLoop是没有线程的,提交之后由线程创建器executor创建线程执行run方法,run方法就是在不断轮询selector中的事件,处理任务队列里面的任务。register就是将NIO的channel和EventLoop里面的selector进行绑定,初始化完成后负责初始化的handler会被移除,此时责任链上还会存在配置中写的handler和处理Acceptor连接事件的handler,此时有4个handler,而头部handler不负责处理实际事件,而是去寻找下一个入站事件的handler,一般为loginhandler(日志记录),然后继续传播,直到一个inboundhandler不继续传播或者进入Tail,

除了register方法还有bind方法,bind方法时出站事件执行顺序和入站事件相反

请求过来以后又是如何处理的呢?我们通过Accept事件获取请求,所以我们应该去看accept入站事件是如何处理的,

首先需要关注的是EventLoop里面的run方法,看run方法中做了什么 ,因为是它在不断轮询,从而散播出事件,拿到事件后调用processSelectedKeys

然后通过processSelectedKeysPlain()方法处理事件,然后取到selectedKey后,去判断事件类型,当为read和accept事件时会调用Unsafe.read();

当服务器收到这样一个accept事件后,EventLoop回去将具体的连接,也就是serversocket对象,变为NIOserversocket对象(netty中socket对象),此时通过轮询得到新链接了,然后散发出read事件,开始责任链的调用,上面服务端上责任链上有四个handler,按照处理器的顺序,处理事件,在loginhandler中用来记录日志,在ServerBootstrap这个处理器中,客户端的连接变为channel对象,这个对象不会是服务端的这个EventLoop处理也不是服务端的这个channel处理,而是交给一个新的EventLoop处理,而这个新的eventloop就是我们在服务器启动时配置的I/O线程组以及accept线程组。

此时新创建的连接数据已经将数据传输过来,就会触发数据读取或者I/O读取的事件,在pipeline分析的关键4要素:什么事件、有哪些处理器、那些会被触发、执行顺序

用户在管道中有一个或者多个channelhandler来接收I/O事件(例如读取)和请求I/O操作(例如写入和关闭)

一个典型的服务器在每个通道的管道中都有以下处理程序,但是根据协议和业务逻辑的复杂性和特征,可能会有所不同:

协议解码器--将二进制数据(例如ByteBuf)转换为Java对象

协议编码器--将java对象转化为二进制数据

业务逻辑处理程序--执行实际的业务逻辑(例如访问数据库)

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值