netty源码二 之Netty入站源码解析

524 阅读4分钟

上一篇讲到了netty服务端的启动流程分析,主要是讲解NioeventLoop的创建过程以及bootstrap.bind(9999)所做的事情。本篇讲解下netty的入站。我们已经知道当服务端启动完成之后,NioEventLoop里面的for(;;)就一直是死循环,主要是做那3件事情。细心的同学可以发现,其实我们在阅读服务端启动源码的step2 processSelectedKeys这一步就已经知道有这么一个入站的操作了,先截一张图如下 image.png 好吧开始准备调试环境,关闭所有断点,先将服务端debug启动,然后在下图处打一个断点 image.png 这里我编写了一个netty的客服端与服务端建立连接,并且客户端发出一条消息到服务端,目的是能让服务端监听到accept事件和read事件,以此方便后续入站过程的调试。 image.png image.png 启动客户端(客户端不要debug启动) image.png 可以看到selectedKeys集合有一个元素,且当前的NioEventLoop是bossGroup。 然后一直往下走进入processSelectedKey方法,可以看到有个readOps,那么此处肯定是连接事件。 image.png Ops是16,进入unsafe.read的read方法 image.png 这个readBuf集合放的就是NioSocketChannel。我们再进入pipeline.fireChannelRead(readBuf.get(i))方法 image.png 接着往下走进入invokeChannelRead方法 image.png 再进入invokeChannelRead(m); image.png 我们看到该方法里面有个channelRead(this,msg);进入channelRead看看里面做了什么 image.png 我们看到channelRead方法里面就只有ctx.fireChannleRead()方法进入该方法 image.png 我们看到这个方法里面有个invokeChannelRead(findContextInbound(MASK_CHANNEL_READ),msg)方法,这个findContextInbound就是从pipeline中查找入站的方法,我们可以进去看一下 image.png 这个mask就是一个入站的标识,然后不断的next找到与之匹配的节点。最终返回这个ctx(这个ctx就是serverBootstrpAcceptor,是一个入站的handler)。退出findContextInbound方法后就接着进入invokeChannelRead方法,然后调用next.invokeChannelRead(m)。请看下图 image.png 进入invokeChannelRead方法 image.png 再进入该方法的channelRead(this,msg)方法,可以看到该channelRead所在的类是ServerBootstrp的内部类,它继承了inBoundHandlerAdapter。接着看这段代码, 我们发现msg是一个通道,然后根据通道获取一个pipeline,再让里面加入一个childHandler image.png childHandler是什么,我们可以看一下 image.png 这个childHandler其实就是我们在NettyServer里面所放入的通道初始化对象, 这个通道初始化对象就是处理服务端我们自定义的业务处理的handler image.png 我们继续往下走,看下childGroup,它实际上就是一个workGroup,然后这里有很重要的一步就是将服务端通道注册到workGroup上。 image.png 继续进入register方法,走到eventLoop.execute。这里的eventLoop就是workGroup image.png image.png 进入eventLoop.execute的execute方法,发现当前线程不是eventLoop,丢进queue,然后进入if里面调用startThread,到这里其实已经和前面的执行流程很相似了,其实就是一个for(;;)的一个轮回 image.png 我们进入startThread方法再看看 image.png image.png 我们看到了有个run方法,再跟进去看看。可以发现这个run里面的for(;;)就是前面netty线程模型图中描述的step1 step2 step3。 image.png 这里要知道这个死循环是workerGroup image.png 接着往下跟代码,我们进入step2 processSelectedKeys,没有进入step1是因为没有事件 image.png 发现step2也没有可以处理的selectedKey(因为此时workGroup还没有将通道进行注册),然后退出来进入进入finally的step3。 image.png 拿到任务,进入safeExecute执行。进入safeExecute方法看看做了什么 image.png 发现是执行run方法里面的 通道注册,进入register0,看到有个doRegister方法。 image.png 进入doRegister方法看看 image.png 我们看看这里的javaChannel获取的是什么。是一个socketChannel,之前注册bossGroup是ServerSocketChannel。然后我们得到socketChannel,就将该通道注册到workGroup的select选择器上。 image.png 继续往下走,执行pipeline.invokeHandlerAddedIfNeeded(); image.png 进入该方法看看,一直往下走 image.png image.png 准备执行一个task任务。进入execute image.png 添加handler image.png 进入ctx.callHandlerAdded() image.png 接着往下走 image.png 进入通道初始化对象 image.png 调用initChannel方法,发现回到了我们NettyServer的通道初始化对象,然后执行里面的initChannel方法,然后添加我们自定义的消息入站的handler。 image.png 一直F8往下走就走完了,到这里就走完了消息入站的流程。以上就是处理完了客户端的连接事件。

读事件流程:
接下来我们还要处理消息read的事件(文章开头我们说到了这里处理了2事件) image.png 一直F8进入step2的processSelectedKey方法走到下面就会有看到Ops事件是OP_READ image.png 进入read方法就来到了这个类的该方法,然后读到客户端发出的消息字节数(30byte) image.png 接着往下走,就会发布处理读取事件,这里是不是就是消息的第一次入站操作呢? image.png 进入fire开头的方法 image.png 继续往下走 image.png 执行next.invokeChannelRead方法,再进入channelRead方法 image.png image.png 往下走 进入invokeChannelRead方法 image.png image.png 再进入这个next.invokeChannelRead(m); image.png 进入这个channelRead方法就到了我们的NettyServerHandler的这个channelRead方法 image.png image.png 接着F8往下走,可以看到read消息的事件是由我们workerGroup去做的 image.png 好了本文到此结束,感谢耐心观看,总结的好累啊。。。