网络编程与Netty(四) Netty源码-(Ⅸ)

Netty事件处理流程

​ 前面在EventLoop系列源码解析中说到了处理各种事件的其实是 NioEventLoop 中的 processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    //校验key的可用性
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                return;
            }
            if (eventLoop != this || eventLoop == null) {
                return;
            }
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
            // 连接connect事件
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }
            //写write事件
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }
            // 接受连接accept事件 或者 读read事件
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

​ 上面是接收到四种事件的处理过程,其实基本都是相同的逻辑,所以我就选择accept事件来讲解下整个流程是怎么样的?也是因为前面我们讲了服务器端的一个启动流程源码,所以选择讲讲它的accept事件处理过程。 unsafe.read()这个方法有两种实现,根据继承关系可以知道服务器的accept事件会调用AbstractNioMessageChannel.NioMessageUnsafe的read方法:其实unsafe.read()这个方法的两种实现在前面Unsafe系列源码讲解时都有详细讲解到

public void read() {
            assert AbstractNioMessageChannel.this.eventLoop().inEventLoop();
            ChannelConfig config = AbstractNioMessageChannel.this.config();
            ChannelPipeline pipeline = AbstractNioMessageChannel.this.pipeline();
            Handle allocHandle = AbstractNioMessageChannel.this.unsafe().recvBufAllocHandle();
            allocHandle.reset(config);
            boolean closed = false;
            Throwable exception = null;
            try {
                int localRead;
                try {
                   //因为可能有多个客户端同时发起连接,所以在do-while循环中调用doReadMessages()
                    do {
                      //整个read方法最重要的也就是这个doReadMessages方法,
                      //执行完后生成的子channel也就加入到readBuf中了
                      //List<Object> readBuf 是实际就是存放与客户端通信的 socketChannel 的容器
                        localRead = AbstractNioMessageChannel.this.doReadMessages(this.readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }
                        allocHandle.incMessagesRead(localRead);
                    } while(allocHandle.continueReading());
                } catch (Throwable var11) {
                    exception = var11;
                }
                localRead = this.readBuf.size();
             //这里遍历生成的子channel加入到,触发ChannelRead事件,具体的流程跟前面提到的fireChannelActive一样,基本没区别
             //ChannelRead事件入站事件,因为在HeadContext和TailContext中的ChannelRead没有做处理 
             //所以进入到ServerBootStrapAccept的channelRead方法,这个方法在前面服务器启动流程中讲解ServerBootStrapAccept时详细说过
             //ServerBootStrapAccept.channelRead的参数msg实际上是一个Channel类型
                for(int i = 0; i < localRead; ++i) {
                    AbstractNioMessageChannel.this.readPending = false;
                    pipeline.fireChannelRead(this.readBuf.get(i));
                }
                this.readBuf.clear();
                allocHandle.readComplete();
            //所有的连接请求都处理完后会调用主handler中的pipeline的fireChannelReadComplete来触发ChannelReadComplete事件(入站)
            //后续流程和fireChannelActive很类似,不在赘述
                pipeline.fireChannelReadComplete();
                if (exception != null) {
                    closed = AbstractNioMessageChannel.this.closeOnReadError(exception);
                    pipeline.fireExceptionCaught(exception);
                }
                if (closed) {
                    AbstractNioMessageChannel.this.inputShutdown = true;
                    if (AbstractNioMessageChannel.this.isOpen()) {
                        this.close(this.voidPromise());
                    }
                }
            } finally {
                if (!AbstractNioMessageChannel.this.readPending && !config.isAutoRead()) {
                    this.removeReadOp();
                }
            }
        }

​ 先进入doReadMessages方法看看,然后在看看ServerBootStrapAccept的channelRead方法(ServerBootStrapAccept这个类后面会提到)

protected int doReadMessages(List<Object> buf) throws Exception {
    //借用SocketUtils.accept方法接受连接,生成一个子channel
        SocketChannel ch = SocketUtils.accept(this.javaChannel());
        try {
            if (ch != null) {
             //将这个子Channel包装成一个NioSocketChannel,然后加入到buf中
             //需要注意的是,在new NioSocketChannel()时,会创建出channel中的pipeline,且会传入将来感兴趣的事件是read事件,因为是子channel
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable var6) {
            logger.warn("Failed to create a new channel from an accepted socket.", var6);
            try {
                ch.close();
            } catch (Throwable var5) {
                logger.warn("Failed to close a socket.", var5);
            }
        }
        return 0;
    }

ServerBootStrapAccept的channelRead方法

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    //这个msg是什么?它是一个子channel,而且包装成NioSocketChannel。
            final Channel child = (Channel)msg;
    	//将提前设置好的handler加入到pipeline中,还有一些其他的ops参数等等
    	//也就是channelRead这个方法完成了channel的一些配置
            child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
            AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
            Entry[] var4 = this.childAttrs;
            int var5 = var4.length;
            for(int var6 = 0; var6 < var5; ++var6) {
                Entry<AttributeKey<?>, Object> e = var4[var6];
                child.attr((AttributeKey)e.getKey()).set(e.getValue());
            }
            try {
               //childGroup.register(child)这个方法,跟服务器的register十分相似,不再赘述
                this.childGroup.register(child).addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable var8) {
                forceClose(child, var8);
            }
        }

​ 至此处理accept事件就完成了所有的流程,其他三种connect、write、read事件处理流程上几乎和处理accept事件的流程大同小异,各位读者可以类比自行解读,也是对自己学习的一种验证方式。

​ 整个Netty现在除了ByteBuf这一块还没讲,其他的都已经讲完了,整体下来笔者还是比较满意的,先将每一个组件中比较重要的内容单独讲解,再大家对每一个细节都有所了解后,在将它们串起来将整体的流程(服务器启动流程)和事件处理流程,因为这些流程是一个比较有连续性的内容,如果一开始就讲这些流程,中途总是卡壳停下来讲每个组件的细节,会让这个过程很不流畅,这样读者就很难坚持下来,会打击读者们的学习积极性,并且效果也不一定好。希望大家有疑问或者笔者理解错的地方在评论区提出,笔者会定期查看消息,会在第一时间回复,共同学习进步。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值