Netty源码分析
Netty模型
Netty对Java的网络IO操作进行了封装,使其更加的易用,在传统的JavaNIO编程模型中,主要由一个线程进行接收连接,一个线程处理事件,另外开启一个线程池对事件进行处理,例如Tomcat的网络模型就是这样的。
Netty对上述的一些概念做了封装,Netty中的主要概念有:
1、EventLoopGroup
2、EventLoop
3、Channel
4、Pipline
5、Handler
EventLoopGroup是一个线程的集合,可以理解为一个线程池,它实现了Executor接口。Netty中的EventLoopGroup分为Boss和Work两种,Boss线程主要用于接收连接和监听事件,BossEventLoopGroup之所以是一个Group是为了处理程序绑定多个端口的情况,一般程序都是单端口的所以Group中的数量是1。WorkerEventLoopGroup则是处理连接事件的集合。
每个EventLoopGroup中有多个EventLoop对象,每个EventLoop对象代表着一个线程,它也实现了线程池对象。
每个EventLoop对应着多个Channel对象,Channel对象是Netty对网络连接的封装,也可以说一个Channel对象属于一个EventLoop,即一个Channel对象的生命周期的执行都会在这个EventLoop上执行,Channel对象中有一个eventLoop属性对应着所属的EventLoop对象。
Channel对象中存在一个链表pipeline,pipeline是一个Handler的链表,存放着对这个Channel处理过程中所有的处理器,从入站到出站的所有处理器集合。
总结:上面的概念属于是对NIO事件驱动模型的一种封装,没有新的东西,但是更加简单,清晰且容易理解。
源码分析
以上模型,主要从两点看下源码分析下,第一个是处理连接和事件的BossEventLoopGroup是怎么处理连接和事件的;第二个是如何让连接在指定EventLoop执行;第三个是Channel处理的整个流程;
1、连接和事件处理循环
Java的服务端socket对象有两个,ServerSocket和ServerSocketChannel,对应BIO和NIO。
通过Netty调试启动一个Nio的服务,在ServerSocketChannel的accept方法处断点,调用服务后,获取调用堆栈:
accept:232, ServerSocketChannelImpl (sun.nio.ch)
run:119, SocketUtils$5 (io.netty.util.internal)
run:116, SocketUtils$5 (io.netty.util.internal)
doPrivileged:-1, AccessController (java.security)
accept:116, SocketUtils (io.netty.util.internal)
doReadMessages:154, NioServerSocketChannel (io.netty.channel.socket.nio)
read:79, AbstractNioMessageChannel$NioMessageUnsafe (io.netty.channel.nio)
processSelectedKey:722, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:658, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:584, NioEventLoop (io.netty.channel.nio)
run:496, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)
可以看出,是在一个线程中执行的,线程名是:nioEventLoopGroup-2-1,应该是BossEventLoopGroup中的线程。EventLoopGroup对象实现了线程池的顶级接口Executor, 我们停掉服务,在Executor接口的execute方法处打一个断点,观察一下对于线程启动的调用。
execute:817, SingleThreadEventExecutor (io.netty.util.concurrent)
register:483, AbstractChannel$AbstractUnsafe (io.netty.channel)
register:87, SingleThreadEventLoop (io.netty.channel)
register:81, SingleThreadEventLoop (io.netty.channel)
register:86, MultithreadEventLoopGroup (io.netty.channel)
initAndRegister:323, AbstractBootstrap (io.netty.bootstrap)
doBind:272, AbstractBootstrap (io.netty.bootstrap)
bind:239, AbstractBootstrap (io.netty.bootstrap)
main:36, Main (com.zanpo.it)
可以看到,实际1个线程的BossEventLoopGroup的实现类是SingleThreadEventExecutor,查看执行的Runnable接口,发现是一个带有promise参数的包装类对象,promise参数里面有一个NioScoketChannel对象,NioSocketChannel里面有一个ServerSocketChannel对象。
应该是在主线程里面创建了一个ServerSocketChannel对象,然后包装成一个NioSocketChannel对象,然后从BossEventLoopGroup线程池里面找一个线程执行任务,流程应该是比较清晰的。
然后再回到上面的ServerSocketChannel的accept方法调用堆栈上来。
accept:232, ServerSocketChannelImpl (sun.nio.ch)
run:119, SocketUtils$5 (io.netty.util.internal)
run:116, SocketUtils$5 (io.netty.util.internal)
doPrivileged:-1, AccessController (java.security)
accept:116, SocketUtils (io.netty.util.internal)
doReadMessages:154, NioServerSocketChannel (io.netty.channel.socket.nio)
read:79, AbstractNioMessageChannel$NioMessageUnsafe (io.netty.channel.nio)
processSelectedKey:722, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:658, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:584, NioEventLoop (io.netty.channel.nio)
run:496, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)
简单看下堆栈,发现是在io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel),这个方法里面在做事件循环,select等待到了READ或者ACCEPT事件后执行accept方法。
执行完毕后,发现有创建新的线程,我们先在accept这里断点,等到断下来后,再在Executor接口的execute方法断下,观察一下接收到连接后是如何进行线程操作的。堆栈如下:
execute:32, ThreadPerTaskExecutor (io.netty.util.concurrent)
execute:57, ThreadExecutorMap$1 (io.netty.util.internal)
doStartThread:986, SingleThreadEventExecutor (io.netty.util.concurrent)
startThread:955, SingleThreadEventExecutor (io.netty.util.concurrent)
execute:838, SingleThreadEventExecutor (io.netty.util.concurrent)
execute0:827, SingleThreadEventExecutor (io.netty.util.concurrent)
execute:817, SingleThreadEventExecutor (io.netty.util.concurrent)
register:483, AbstractChannel$AbstractUnsafe (io.netty.channel)
register:87, SingleThreadEventLoop (io.netty.channel)
register:81, SingleThreadEventLoop (io.netty.channel)
register:86, MultithreadEventLoopGroup (io.netty.channel)
channelRead:215, ServerBootstrap$ServerBootstrapAcceptor (io.netty.bootstrap)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:97, AbstractNioMessageChannel$NioMessageUnsafe (io.netty.channel.nio)
processSelectedKey:722, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:658, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:584, NioEventLoop (io.netty.channel.nio)
run:496, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)
可以看到Runable接口的包装对象里面放了个NioEventLoop对象,NioEventLoop对象里面有个selector成员存放着相关的fd信息,我这里是kqueue实现,kqueue是mac的异步IO实现,类比Linux下的epoll。
再在SocketChannel的read方法处断下观察下。
read:295, SocketChannelImpl (sun.nio.ch)
setBytes:258, PooledByteBuf (io.netty.buffer)
writeBytes:1132, AbstractByteBuf (io.netty.buffer)
doReadBytes:357, NioSocketChannel (io.netty.channel.socket.nio)
read:151, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:722, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:658, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:584, NioEventLoop (io.netty.channel.nio)
run:496, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)
在新创建的线程中断下,发现新线程中也在执行事件循环,主要监听已连接的socket的事件。
简单比较一下堆栈,发现boss线程和work线程都是在线程循环执行NioEventLoop的代码,就是在processSelectedKey:722, NioEventLoop (io.netty.channel.nio)这里进行了分流,主要是调用了ch.unsafe()返回类的read方法,从堆栈可以看出,接收连接的ServerSocketChannel的unsafe是AbstractNioMessageChannel$NioMessageUnsafe类,而接收到客户端连接SocketChannel是AbstractNioByteChannel$NioByteUnsafe类。应该就是区分ServerSocket和Socket事件的多态实现了,一个执行accept一个执行read。
看到这里,连接和事件处理循环的流程就比较清晰了
总结:首先在主线程中创建ServerSocketChannel对象,包装成NioEventLoop对象丢到Boss线程池执行,执行主要是进行事件循环监听连接事件,连接到来时,将接收到的连接封装成NioEventLoop对象,丢到work线程池中执行。在work线程中也是执行事件循环,对socket的事件进行监听然后处理。
2、数据的流转路径
写一个Handler,然后channelRead时断点,堆栈如下:
channelRead:20, ServerSocketHandler (com.zanpo.it.handler)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:722, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:658, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:584, NioEventLoop (io.netty.channel.nio)
run:496, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)
观察read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)发现,在work线程中事件循环监听到事件到来,会从NioSocketChannel对象中找到对应的pipline对象,pipline对象时一个链表,存放了对应的handler对象。
在invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)中发现,会讲handler对象转成ChannelInboundHandler对象执行channelRead方法,链式执行,由上一个handler去执行下一个handler。
执行完毕之后继续进行事件循环。
总结
Netty又是一种新的事件循环模式,不同于Tomcat,Tomcat是一个Accept线程等待连接,一个Poll线程等待连接事件,一个线程池进行事件处理。
而Netty是一个一个Boss线程等待连接,接收到的连接丢给work线程池,work线程池中的每个线程都对分配给本线程的连接进行事件循环监听,监听到事件后进行相应处理,然后继续事件循环。
Netty这种模型相当于将连接与单个work线程进行了绑定,并在work线程中进行事件循环,模型简单明了,也利于Netty中的channel等相关概念的实现。但是如果channel中存在阻塞,将会影响被分配给这个线程的所有连接。

浙公网安备 33010602011771号