Netty源码深度解析

今天,聊一聊Netty源码。Netty在我们Java圈中挺出名的,因为其高性能,号称支持百万的客户端连接,因此,很多框架通讯都是采用了Netty,如Dubbo、Zookeeper、Spring WebFlux等等。简单来说,Netty实际上是对Java原生NIO的封装,因为Java提供的原生NIO用起来确实不太方便,因此Netty做了封装,使得我们在使用Netty的时候,写一些模板性的代码即可,并且只需要添加一个或多个我们自己写好的ChannelHandler类,在对应的方法中,写好业务逻辑即可,而不必关注NIO使用上的一些细节。先看看我写的,以Netty服务端为例,如下图所示:
在这里插入图片描述
这是一个最简单的Netty Server,并且只有NettyServerHandler是我写的,它继承自SimpleChannelInboundHandler抽象类,如下图所示:
在这里插入图片描述
回到NettyServer类中,先看NioEventLoopGroup,这是Netty提供的类,可以把它理解为是线程池,当然和我们常用的线程池还是有很大的不同的,创建NioEventLoopGroup,该类提供了有参构造和无参构造,如果是有参的话,传入一个具体的数值,就是指明创建多少个NioEventLoop,如果是无参构造的话,默认是创建CPU核心数×2,这块将在代码中体现,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以上就是NioEventLoopGroup构造方法的调用链路,其中,在它的父类MultithreadEventLoopGroup中,就会判断传入的nThreads是否为空,为空则是CPU核心数×2,不为空则以传入的nThreads为准,如下图所示:
在这里插入图片描述
在该构造方法中,先是判断传入的executor是否为空,为空则初始化一个ThreadPerTaskExecutor对象并赋值给executor,看看ThreadPerTaskExecutor,如下图所示:
在这里插入图片描述
由此可以看到,调用ThreadPerTaskExecutor#execute()方法,传入一个Runnable对象,它就会把我们传入的Runnable对象做一下包装(实际上就是创建一个Thread对象,把Runnable对象作为入参传入),并调用start方法,启动线程。回到MultithreadEventExecutorGroup的有参构造中,继续往下看,然后就是初始化他的成员属性children,它是一个EventExecutor类型的数组,接着就是根据我们传入的线程数做循环,在循环中调用MultithreadEventExecutorGroup#newChild()方法,给数组中的每一个元素赋值,看看该方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
创建的是NioEventLoop,调用的是有参构造,传入了NioEventLoopGroup对象、ThreadPerTaskExecutor对象、SelectorProvider对象、DefaultSelectStrategyFactory对象、RejectedExecutionHandler对象、EventLoopTaskQueueFactory对象、EventLoopTaskQueueFactory对象。进到构造方法中看看,如下图所示:
在这里插入图片描述
构造中调用了父类的构造,最好也点进去看看,可能会有一些初始化的逻辑,如下图所示:
在这里插入图片描述
在这里插入图片描述
上图中的父类构造中,初始化了一个taskQueue成员属性,这是一个阻塞队列,通常用来做异步操作的,挺重要的,先记下来,后续很可能会用到,继续看父类的构造,如下图所示:
在这里插入图片描述
在这里插入图片描述
到上图就没什么可看的了,就是把NioEventLoopGroup做了一下赋值而已。到这里,NioEventLoopGroup的构造方法,及其父类的构造方法全部看完了,回到NettyServer类中:如下图所示:
在这里插入图片描述
接着就是创建ServerBootstrap对象,并且通过链式调用,给ServerBootstrap属性赋值,包括调用ServerBootstrap#group()方法,将上面创建的两个NioEventLoopGroup对象传入,可以点进去看看,如下图所示:
在这里插入图片描述
再看看父类的group()方法:
在这里插入图片描述
这里就可以记录一下:我们创建了两个NioEventLoopGroup对象,分别是 bossGroup 和 workerGroup,分别给ServerBootstrap的group属性和childGroup属性进行了赋值。再看看ServerBootstrap#channel()方法,传入的是NioServerSocketChannel.class,如下图所示:
在这里插入图片描述
再看看,最终就是给ServerBootstrap的channelFactory属性赋值,赋值的对象是ReflectiveChannelFactory,创建该对象的时候调用的是有参构造,传入的就是NioServerSocketChannel.class,这个有参构造就不看了,后续还会使用到ReflectiveChannelFactory。再看看ServerBootstrap#childHandler()方法,传入ChannelInitializer的实现类,如下图所示:
在这里插入图片描述
就是把传入的ChannelInitializer的实现类赋值给childHandler属性。以上就是ServerBootstrap类创建后的赋值,再来看ServerBootstrap#bind()方法,这个方法很重要,涉及到了Netty怎么启动注册并且绑定端口,点进去看看,如下图所示:
在这里插入图片描述
最终调用到了ServerBootstrap#doBind()方法,在该方法中发有一个ServerBootstrap#initAndRegister()方法,一般方法中带有"init"、"start"等单词的需要重点关注下,点到该方法中看看它干了些啥,如下图所示:
在这里插入图片描述
发现用到了ServerBootstrap的channelFactory属性,这个属性不就是前面我说的ReflectiveChannelFactory类的对象吗,那就看看ReflectiveChannelFactory#newChannel()方法,如下图所示:
在这里插入图片描述
会发现就干了一件事,通过反射创建了某个对象,而这个constructor其实就是之前传入的NioServerSocketChannel.class的默认构造,因此创建的也是NioServerSocketChannel对象,可以看看NioServerSocketChannel类的构造方法,看看构造方法中有没有做一些初始化,或者赋值之类的,如下图所示:
在这里插入图片描述
在上图中,比较核心的就是NioServerSocketChannel#newChannel()方法,通过这个方法可以得到一个ServerSocketChannel对象,而这个对象实际上就是JDK提供的类。如果我们不用Netty,直接用JDK提供的原生类开发NIO,就必然会用到这个类,所以说,Netty本质上就是用JDK原生的NIO做了封装,使之用起来更简单方便。在最后的有参构造中就接收了ServerSocketChannel对象,并且构造方法中调用了父类的构造,传入了该对象和SelectionKey.OP_ACCEPT,第二个参数,看过JDK原生代码的应该知道,这表示,服务端接收的是客户端的连接请求,除了SelectionKey.OP_ACCEPT之外,还有下图中的几个:
在这里插入图片描述
分别表示不同的ServerSocketChanne/SocketChannel上发生的不同事件类型。另外一个就是创建的是NioServerSocketChannelConfig对象,并给属性config赋值,调用的是它的有参构造,传了两个参数:一个是NioServerSocketChannel它自己,另一个是ServerSocketChannel对象,就是之前通过NioServerSocketChannel#newChannel()方法所得到的,如下图所示:
在这里插入图片描述
再看看NioServerSocketChannel父类的javaChannel()方法,即AbstractNioChannel#javaChannel()方法,如下图所示:
在这里插入图片描述
这个ch属性,实际上就是在之前调用NioServerSocketChannel父类,在父类中赋值的,具体如下图所示:
在这里插入图片描述
这个ch实际就是ServerSocketChannel对象,所以AbstractNioChannel#javaChannel()方法得到的也就是ServerSocketChannel对象,通过ServerSocketChannel#socket()方法,得到的就是ServerSocket对象。ServerSocket对象就是真正跟客户端建立连接,并生成服务端的Socket对象,这样就可以跟客户端通信了。AbstractNioMessageChannel的构造,又调用了父类的构造,继续往上看,如下图所示:
在这里插入图片描述
在这里就可以看到,ch就是在这里赋值的,并且调用ch的方法,即SelectableChannel#configureBlocking()方法,传入false,表示以非阻塞的形式运行,不这样设置会报错,并把SelectionKey.OP_ACCEPT赋值给了readInterestOp属性。再看看父类,继续往上看,如下图所示:
在这里插入图片描述
看上面的构造,可知传入的parent属性是null,并且初始化了两个非常重要的属性,即:unsafe和pipeline,看看这两个属性,具体赋值的哪两个对象。先看unsafe,现在NioServerSocketChannel找一下newUnsafe()方法,发现没有,在它的父类AbstractNioMessageChannel找一下,刚好就找到了,如下图所示:
在这里插入图片描述
因此,unsafe属性实际上赋值的是NioMessageUnsafe对象;再看看pipeline属性,同样的找法,发现newChannelPipeline()方法,就在本类中,即AbstractChannel类中,如下图所示:
在这里插入图片描述
因此,pipeline属性是DefaultChannelPipeline对象,并传入了this,即NioServerSocketChannel对象。又看了一下NioMessageUnsafe和DefaultChannelPipeline,发现NioMessageUnsafe的构造中没什么逻辑,但是DefaultChannelPipeline是有以下逻辑,需要点进DefaultChannelPipeline的构造中看看,如下图所示:
在这里插入图片描述
初始化了两个成员属性:head和tail,head是HeadContext对象,tail是TailContext对象,都是调用的有参构造,传入的是this,即DefaultChannelPipeline。然后通过next属性和prev属性,把head和tail关联了起来,说白了就是构成了head和tail的双向链表。其中HeadContext和TailContext都是继承自AbstractChannelHandlerContext,如下图所示:
在这里插入图片描述
进入AbstractChannelHandlerContext的构造中看看,如下图所示:
在这里插入图片描述
在这里插入图片描述
也没做什么,就只是做了下简单的赋值,如给pipeline属性赋值,还有executor,不过executor传入的为null,等等。到现在为止,NioServerSocketChannel的初始化就讲完了,回到ServerBootstrap#initAndRegister()方法中,看到了ServerBootstrap#init()方法,如下图所示:
在这里插入图片描述
看看ServerBootstrap#init()方法,如下图所示:
在这里插入图片描述
里面主要是拿到ServerBootstrap的两个属性childGroup和childHandler,然后调用pipeline的addLast()方法,传入一个ChannelInitializer对象,在ChannelInitializer#initChannel()方法中,最核心的也是调用了调用pipeline的addLast()方法,放入了一个ServerBootstrapAcceptor对象。继续往下看,然后对于catch中的内容属于分支逻辑,暂时不用看,看看ServerBootstrap#config()方法,返回的是什么对象,如下图所示:
在这里插入图片描述
看看config属性是在哪里赋值的,如下图所示:
在这里插入图片描述
可以知道,直接就创建了ServerBootstrapConfig对象,调用有参构造传入了this,即ServerBootstrap对象。最终,调用ServerBootstrapConfig#group()方法,实际上就是调用的是ServerBootstrapConfig中的bootstrap属性的group()方法,而bootstrap就是刚刚我们传入的ServerBootstrap,即调用ServerBootstrap#group()方法,得到的就是之前我们创建的NioEventLoopGroup bossGroup属性,如下图所示:
在这里插入图片描述
在这里插入图片描述
最终调用的就是EventLoopGroup#register()方法,传入的是NioServerSocketChannel对象。看看EventLoopGroup#register()方法是什么逻辑,如下图所示:
在这里插入图片描述
NioEventLoopGroup的父类是MultithreadEventLoopGroup,因此看MultithreadEventLoopGroup#register()方法,如下图所示:
在这里插入图片描述
看看MultithreadEventLoopGroup#next()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个chooser,实际上就是之前在MultithreadEventExecutorGroup的有参构造中初始化的,并且把初始化的children属性传进去了,这个属性是EventExecutor数组,如下图所示:
在这里插入图片描述
因此这个选择器就会从传入的EventExecutor数组中按照一定的规则,选出一个EventExecutor来,也就是NioEventLoop对象,回到register()方法中,如下图所示:
在这里插入图片描述
SingleThreadEventLoop就是NioEventLoop的父类,如下图所示:
在这里插入图片描述
在这里插入图片描述
看看AbstractChannel#register()方法,传入了NioEventLoop对象和DefaultChannelPromise对象,如下图所示:
在这里插入图片描述
看这个方法,里面有两个if判断逻辑,进入之后都是直接返回了,这种的话,其实没必要看了,一般不会进去,不然这个register()方法就相当于啥事没干了。然后就是把传入的NioEventLoop对象赋值给eventLoop属性。下面又是一个判断if/else的判断,这个需要看一下,其实不管是if还是else,最终都会调用 AbstractChannel#register0()方法,想抓主线逻辑的话可以直接看这个方法。不过还是看看if中的条件吧,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于thread此时还是为空的,当然就不等于”当前线程”了,所以if中的逻辑为false,进入else逻辑,继续往下看,就调用到了的NioEventLoop#execute()方法,传入一段λ表达式,实际上就是Runnable对象,Runnable#run()方法就只是调用了AbstractChannel#register0()方法,传入DefaultChannelPromise对象。点到该方法中看看,如下图所示:
在这里插入图片描述
可知最终进入到了execute()方法的两个的入参的重载方法,里面又调用了两个方法,分别是:SingleThreadEventExecutor#addTask()方法 和 SingleThreadEventExecutor#startThread()方法,当然,SingleThreadEventExecutor是NioEventLoop的父类。前一个方法把Runable作为入参传进去了,如下图所示:
在这里插入图片描述
最终是把Runnable对象放入了taskQueue属性中,它是阻塞队列,之前在创建NioEventLoop对象的时候在它的构造方法中初始化的。再看后一个方法,如下图所示:
在这里插入图片描述
这里面中只有一个if逻辑,直接看里面的逻辑即可,里面有一个SingleThreadEventExecutor#doStartThread()方法,这个方法看起来就像是真正有注册的业务逻辑,继续进去看看,如下图所示:
在这里插入图片描述
方法很长,没截取完,核心就是通过SingleThreadEventExecutor的executor属性调用executor#execute()方法,传入Runnable对象,启动一个线程,在Runnable对象的run()方法中,又是一大段的try/catch逻辑,catch里面是分支逻辑,暂时不用看,只然后就是把当前线程赋值给了SingleThreadEventExecutor的thead属性,这样的话,后续SingleThreadEventExecutor#inEventLoop()方法,就返回true了。再看SingleThreadEventExecutor#inEventLoop()方法,就返回true了。SingleThreadEventExecutor.this#run()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
该方法,又是一大段逻辑,其中一段是switch来处理的,如果有小伙伴接触或Java的NIO,会有这么个方法:selector.select(),实际上就是调用操作系统的epoll相关的系统函数,意思是获取那些有读/写事件发生的Socket,进而进行处理。因此这里可以大胆的猜一下,switch满足的是SelectStrategy.SELECT条件,里面有一个重要的逻辑,是判断taskQueue队列是否为空,前面不是已经往里面放了一个Runnable对象吗,因此不满足if的条件。继续往下,又是一段if/else的逻辑,判断ioRatio和strategy的值,这种其实就可以打断点看看,静态看源码就不适用了,这里就是进入到下面的方法,如下图所示:
在这里插入图片描述
进入到NioEventLoop#processSelectedKeys()方法中看看,如下图所示:
在这里插入图片描述
这里判断selectedKeys是否为空,这个之前在创建NioEventLoop对象的时候,构造中赋值过了,所以不为空,所以进入到if逻辑中,继续往下看,如下图所示:
在这里插入图片描述
由于此时还没有客户端连接过来,所以selectedKeys集合为空,跳出for循环。回到NioEventLoop#run()方法,如下图所示:
在这里插入图片描述
由于是finally,所以NioEventLoop#runAllTasks()方法一定会执行,进入该方法看看,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由上图可知最终就是从taskQueue队列中拿到之前我们传入的Runnable对象,并调用Runnable#run()方法,所以最后还是要回到AbstractChannel#register()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
在该方法中,AbstractChannel#doRegister()方法,点进去看看,如下图所示:
在这里插入图片描述
在该方法中,核心的只有一行:就是调用AbstractNioChannel#javaChannel()方法,拿到ServerSocketChannel对象,再调用AbstractNioChannel#eventLoop()#unwrappedSelector()方法,拿到一个Selector对象,并将ServerSocketChannel对象注册到Selector对象上,传入0,表示ServerSocketChannel对象对连接事件”感兴趣”。再回到AbstractChannel#register0()方法中,如下图所示:
在这里插入图片描述
先看看DefaultChannelPipeline#invokeHandlerAddedIfNeeded()方法,如下图所示:
在这里插入图片描述
再看看DefaultChannelPipeline# callHandlerAddedForAllHandlers()方法,如下图所示:
在这里插入图片描述
先看看DefaultChannelPipeline的pendingHandlerCallbackHead属性是怎么赋值的:
在这里插入图片描述
发现是调用了DefaultChannelPipeline#callHandlerCallbackLater()方法,再看看这个方法是在哪里被调用的:
在这里插入图片描述
调用的地方很多,先随便点进去看看,以第一个为例:
在这里插入图片描述
原来是调用了DefaultChannelPipeline#addFirst()方法,但是这个方法我们没有用到,我们用到的是DefaultChannelPipeline#addLast()方法,可以找一下这个之前调用这个方法的地方,往下找,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终也会看到DefaultChannelPipeline#callHandlerCallbackLater()方法,传入的是DefaultChannelHandlerContext和true,点进方法中看看,如下图所示:
在这里插入图片描述
由于传入的最终就是added为true,则创建的是PendingHandlerAddedTask对象,调用它的有参构造,传入DefaultChannelHandlerContext对象。如果多次调用DefaultChannelPipeline#addLast()方法,有代码可知,会构建一个单向链表。回到DefaultChannelPipeline#callHandlerAddedForAllHandlers()方法中,如下图所示:
在这里插入图片描述
就是调用PendingHandlerAddedTask#exeute()方法,并且通过next属性找到下一个PendingHandlerAddedTask对象,继续调用PendingHandlerAddedTask#exeute()方法,直到链表的尾节点为止。看看PendingHandlerAddedTask#exeute()方法,如下图所示:
在这里插入图片描述
再看DefaultChannelPipeline#callHandlerAdded0()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
看看AbstractChannelHandlerContext#handler()方法返回的是什么对象:实际上也跟之前调用DefaultChannelPipeline#addLast()方法有关,具体调用链路如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由此可以看出,调用的是ChannelInitializer#handlerAdded()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ChannelInitializer#initChannel()方法实际上是一个抽象方法,供子类实现的,也就是我们创建ChannelInitializer的实现类的时候,实现的initChannel()方法,在该方法的finally中,调用了pipeline.remove(this),移除了ChannelInitializer对象,说白了ChannelInitializer类的作用就只是往pipeline中添加ChannelHandler,如下图所示:在这里插入图片描述
也就是在这里面,通过传入的SocketChannel,拿到pipeline,进而把我们写的ChannelHandler加入到pipeline的双向链表中。可以是看看怎么添加的,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看出,创建的DefaultChannelHandlerContext对象,调用其有参构造,传入ChannelHandler对象和DefaultChannelPipeline对象,并且DefaultChannelHandlerContext对象会放在tail结点之前,并通过next和prev进行关联。再回到AbstractChannel#register0()方法,如下图所示:

再看看pipeline#fireChannelRegistered()方法中,调用的是AbstractChannelHandlerContext.invokeChannelRegistered()方法,传入的是head,即HeadContext对象,记住,下面会用到。如下图所示:
在这里插入图片描述
在这里插入图片描述
AbstractChannelHandlerContext#invokeChannelRegistered()方法中,有if/else的逻辑,看看应该走哪个逻辑,看看AbstractChannelHandlerContext#invokeHandler()方法,如下图所示:
在这里插入图片描述
看看handlerState属性的值,实际上在前面已经赋值了,如下图所示:
在这里插入图片描述
在这里插入图片描述
由上图可知,是通过CAS赋值的。回到AbstractChannelHandlerContext#invokeChannelRegistered()方法中,走的是if逻辑中的代码,如下图所示:在这里插入图片描述
上面传入的是head,因此应该进入HeadContext#channelRegistered()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
DefaultChannelPipeline#invokeHandlerAddedIfNeeded()方法不会被调用,看看该方法为什么不会被调用,如下图所示:
在这里插入图片描述
firstRegistration为false,因为在AbstractChannel#register0()方法中调用过了,并且将firstRegistration设置成了false。再看第二个方法,即HeadContext#fireChannelRegistered()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
看看AbstractChannelHandlerContext#findContextInbound()方法,如下图所示:
在这里插入图片描述
也就是拿到HeadContext的下一个节点,也就是我们添加的自定义的ChannelHandler所在的DefaultChannelHandlerContext,如果没有添加,得到的下一个节点就是TailContext,并返回。再调用AbstractChannelHandlerContext#invokeChannelRegistered()方法,传入的则是HeadContext的下一个节点,如下图所示:
blog.csdnimg.cn/direct/c39f7bb9008e46d294f615458b7390c7.png)
简单来说:就是从head节点开始,一个个往后调用,直到调用的tail为止,同时会调用到我们自己写的ChannelHandler#channelRegistered()方法,如果有实现的话,如下图所示:
在这里插入图片描述
在这里插入图片描述
就可以执行我们的业务逻辑了。总结一下:如果我们编写的ChannelHandler实现了ChannelInboundHandler,则调用顺序是从head–>…–>tail;如果实现的是ChannelOutboundHandler,则调用顺序是从tail–>…–>head,并且都会经过我们自己写的ChannelHandler。当然,这仅对于方法名是"fireXxxx"类似这种,如下图所示:
在这里插入图片描述
再回到AbstractChannel#register0()方法中,如下图所示:
在这里插入图片描述
pipeline.fireChannelActive()方法的调用逻辑亦是如此,只不过pipeline.fireChannelActive()方法和pipeline.fireChannelRegistered()方法一样,只会在客户端连接到服务端的时候,调用一次。其实这个时候,还没有真正的将我们自己编写的ChannelHandlerf放入pipeline中,因为此时还没有客户端连接,也就不会调用到ServerBootstrapAcceptor#channelRead()方法。
在这里插入图片描述
这时,也只是在ServerBootstrap#init()方法中,将ServerBootstrapAcceptor对象所在的DefaultChannelHandlerContext对象加入到pipeline,准确地讲,是NioServerSocketChannel的pipeline,如下图所示:
在这里插入图片描述
那在什么时候,才会真正的触发ServerBootstrapAcceptor#channelRead()方法呢?这还是要回到NioEventLoop#processSelectedKeysOptimized()方法中,如下图所示:
在这里插入图片描述
看看NioEventLoop#processSelectedKey()方法中,如下图所示:
在这里插入图片描述
在这里插入图片描述

假设此刻有一个客户端发起了连接,这时候selector#select()方法,就会感知到一个SelectionKey.OP_ACCEPT的事件,服务端就会得到一个与这个客户端进行通讯的NioSocketChannel,会进到图中框起来的if逻辑中(SelectionKey.OP_READ或者SelectionKey.OP_ACCEPT都会进入到if中),进而调用unsafe.read()方法,如下图所示:
在这里插入图片描述
前面讲过,这个NioServerSocketChannel的unsafe属性实际上就是AbstractNioMessageChannel的子类,继续看AbstractNioMessageChannel#read()方法,如下图所示:
在这里插入图片描述
进入AbstractNioMessageChannel#doReadMessages()方法中,传入一个List readBuf对象,如下图所示:
在这里插入图片描述
再看看SocketUtils#accept()方法,如下图所示:
在这里插入图片描述
这不就是Java NIO的原生代码吗?得到的是SocketChannel对象,不就是前面我说的“服务端就与客户端进行通讯的NioSocketChannel”吗。然后就会将SocketChannel对象放入readBuf中,回到 AbstractNioMessageChannel#read()方法中,如下图所示:
在这里插入图片描述
就会到用到pipeline.fireChannelRead()方法,而入参则是SocketChannel对象,也包括后面的 pipeline.fireChannelReadComplete()方法,如果发生了异常,则调用pipeline.fireExceptionCaught()方法,而调用pipeline.fireChannelReadComplete()方法,最终就会调用到ServerBootstrapAcceptor#channelRead()方法,进而将我们自己编写的ChannelHandler放入NioSocketChannel中的pipeline属性中,如下图所示:
在这里插入图片描述
在ServerBootstrapAcceptor#channelRead()方法还有一个核心的方法,即:childGroup.register()方法,入参则是SocketChannel对象,而这个childGroup实际上就是之前我们创建的EventLoopGroup workerGroup,如下图所示:
在这里插入图片描述
由于也是EventLoopGroup,因此注册逻辑跟上文中注册NioServerSocketChannel类似,这里就不多讲了。不同的是NioSocketChannel继承自AbstractNioByteChannel,而NioServerSocketChannel继承自AbstractNioMessageChannel,因此,AbstractNioChannel#read()方法的实现是不同的,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
AbstractNioMessageChannel#read()前文已讲过,这里不再赘述,关键是看看AbstractNioByteChannel#read()方法,也就是客户端的发送数据到服务端的时候,会调用该方法,如下图所示:
在这里插入图片描述
从上图中可以看出,客户端发送的数据,会被写入到ByteBuf对象中,最后再调用pipeline。fireChannelRead()方法,传入ByteBuf对象,然后我们自己编写的ChannelHandler实现了channelRead()方法,就可以拿到客户端发送过来的数据了,进而处理我们自己的业务逻辑,如下图所示:
在这里插入图片描述
在这里插入图片描述
再回到AbstractNioByteChannel#read()方法中,再看看allocHandle.allocate()方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在AbstractByteBufAllocator#ioBuffer()方法中,实际上进的是if逻辑,最终得到是的directBuffer,而不是heapBuffer。当然,也可以配置Netty参数 ,让它不进if逻辑,一般不这样做,因为directBuffer使用的是零拷贝技术,也就是说,客户端发送的数据,到了网卡,然后操作系统将网卡的数据,读到内存中,而此时,在堆中,比如directBuffer对象其实只是有一个类似于“address”的属性,它存储的是一个引用地址,指向了客户端发送的数据所存储的内存地址,相比于heapBuffer,它其实内部会有一个byte数组属性,用于存储客户端发送的数据,这种的话,就需要把客户端发送的数据所存储的内存中再次拷贝到堆内存中,并储存在byte数组中。这样的话,相比于directBuffer,heapBuffer就多了一次数据从内存–>堆的拷贝,同时服务端向客户端发送数据,也是多了一次从堆内存–>内存的拷贝,降低了速度。并且,不管是堆内存–>内存的拷贝,还是内存–>堆的拷贝,Java还会调用Linux的系统函数处理数据,这还涉及到了用户态到内核态之间的来回切换,同样也降低了速度。因此,Netty使用零拷贝技术,也是Netty号称可以支撑百万连接的原因之一。
最后做一个小的总结,下图是我在网上找的,充分说明了Netty框架的运行模式,它是基于Reactor模型的主从架构:需要创建两个线程组EventLoopGroup,即bossGroup和workerGroup,一般bossGroup设置的线程组中线程的数量为1(也可以设置>1,如果这样设置的话,就需要绑定其他的端口,设置几个,绑定几个端口),workerGroup设置的线程组是多个,不设置默认创建的线程数量为 CPU核心数×2。并且,bossGroup中的线程会处理NioServerSocketChannel,NioServerSocketChannel只处理连接事件,当有客户端连接过来,则会产生一个对应的NioSocketChannel,并从workerGroup中按照一定的算法(默认是轮询),从中选择一个线程,处理NioSocketChannel,而NioSocketChannel主要是处理读写事件,用户发过来数据,接收数据,并且通过我们自己编写的ChannerHandler去处理,并将处理的结果,再通过NioSocketChannel返回给客户端。至于是怎么感知到读写事件,这就依赖于Linux的epoll系统函数,这块以后有机会再聊聊。以上,就是我对Netty框架的一些拙见,如有错误,欢迎各位大佬批评指正,我会及时修正,在此感激不尽!
在这里插入图片描述

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Netty5.0 架构剖析和源码解读 作者:李林锋 版权所有 email neu_lilinfeng@ © Netty5.0 架构剖析和源码解读1 1. 概述2 1.1. JAVA 的IO演进2 1.1.1. 传统BIO通信的弊端2 1.1.2. Linux 的网络IO模型简介4 1.1.3. IO复用技术介绍7 1.1.4. JAVA的异步IO8 1.1.5. 业界主流的NIO框架介绍10 2.NIO入门10 2.1. NIO服务端10 2.2. NIO客户端13 3.Netty源码分析16 3.1. 服务端创建16 3.1.1. 服务端启动辅助类ServerBootstrap16 3.1.2. NioServerSocketChannel 的注册21 3.1.3. 新的客户端接入25 3.2. 客户端创建28 3.2.1. 客户端连接辅助类Bootstrap28 3.2.2. 服务端返回ACK应答,客户端连接成功32 3.3. 读操作33 3.3.1. 异步读取消息33 3.4. 写操作39 3.4.1. 异步消息发送39 3.4.2. Flush操作42 4.Netty架构50 4.1. 逻辑架构50 5. 附录51 5.1. 作者简介51 5.2. 使用声明51 1. 概述 1.1.JAVA 的IO演进 1.1.1. 传统BIO通信的弊端 在JDK 1.4推出JAVANIO1.0之前,基于JAVA 的所有Socket通信都采用 BIO 了同步阻塞模式( ),这种一请求一应答的通信模型简化了上层的应用开发, 但是在可靠性和性能方面存在巨大的弊端。所以,在很长一段时间,大型的应 C C++ 用服务器都采用 或者 开发。当并发访问量增大、响应时间延迟变大后, 采用JAVABIO作为服务端的软件只有通过硬件不断的扩容来满足访问量的激 增,它大大增加了企业的成本,随着集群的膨胀,系统的可维护性也面临巨大 的挑战,解决这个问题已经刻不容缓。 首先,我们通过下面这幅图来看下采用BIO 的服务端通信模型:采用BIO 通信模型的 1connect NewThread1 WebBrowse 2connect 2handle(Req) WebBrowse 3connect Acceptor NewThread2 WebBrowse WebBrowse 4connect NewThread3 3sendResponsetopeer NewThread4 图1.1.1-1 BIO通信模型图 服务端,通常由一个独立的Accepto 线程负责监听客户端的连接,接收到客户 端连接之后为客户端连接创建一个新的线程处理请求消息

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值