看到這篇文章的應(yīng)該都用過(guò)Netty吧乡小。Netty服務(wù)端的模板代碼如下翅溺,我們分析下它是怎么啟動(dòng)的。不要糾結(jié)沒(méi)有關(guān)閉連接的代碼诀黍,畢竟我們只是用這段代碼來(lái)debug袋坑。這篇文章我主要寫(xiě)的是Netty服務(wù)端的啟動(dòng)流程。
讀完這篇文章你會(huì)知道:
- Netty的幾大組件的關(guān)系是什么蔗草?包括NioEventLoopGoup, NioEventLoop, Channle, ChannelHandler, Pipeline
- Netty是怎么注冊(cè)感興趣的事件的咒彤?
- Netty服務(wù)的bossGroup收到連接后疆柔,是怎么轉(zhuǎn)派到workerGroup的咒精。
- Netty服務(wù)端啟動(dòng)時(shí)經(jīng)歷了什么?
package netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 28)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MyChatHandler());
}
});
ChannelFuture sync = bootstrap.bind(6666).sync();
sync.channel().closeFuture().sync();
}
}
EventLoopGroup事件循環(huán)組
Netty是基于Reactor事件模型的旷档,它將通道的連接和處理分開(kāi)模叙,bossGroup負(fù)責(zé)處理NioServerSocketChannel通道連接,通道連接后生產(chǎn)NioSocketChannel分派到workerGroup處理通道的讀寫(xiě)事件和業(yè)務(wù)邏輯鞋屈。具體怎么通道怎么分配的范咨,接下來(lái)再說(shuō)明故觅。
EventLoopGroup顧名思義,它就是EventLoop組渠啊,維護(hù)一堆EventLoop的输吏,如下圖(NioEventGroup實(shí)現(xiàn)了EventExcutor接口)。默認(rèn)new EventLoopGroup()會(huì)生成(cpu核心數(shù)*2)個(gè)EventLoop替蛉,每個(gè)EventLoop綁定一個(gè)端口贯溅,所以如果服務(wù)只綁定一個(gè)端口的話,就指定為1個(gè)接口躲查,即new EventLoopGroup(1);
NioEventLoop事件循環(huán)
每個(gè)EventLoop都有一個(gè)死循環(huán)它浅,負(fù)責(zé)監(jiān)聽(tīng)Channel上的事件×椭螅看NioEventLoop的繼承鏈姐霍,它是一個(gè)EventExecutor、EventLoop典唇,所以NioEventLoop是一個(gè)線程池镊折。NioEventLoop的父類SingleThreadEventExecutor維護(hù)了一個(gè)線程池對(duì)象介衔。
NioEventLoop的execute(Runable r)方法,這是線程次的提交方法与纽,當(dāng)提交一個(gè)線程到NioEventLoop中時(shí),就是執(zhí)行這個(gè)方法塘装,它做的工作如下圖,是將提交過(guò)來(lái)的線程task入隊(duì)蹦肴,然后執(zhí)行NioEventLoop的線程提交方法execute,先將task入隊(duì)阴幌,再看情況執(zhí)行startThread()勺阐。
跟蹤進(jìn)去矛双,它會(huì)執(zhí)行doStartThread()方法议忽。這個(gè)方法提交一個(gè)線程,立馬執(zhí)行了SingleThreadEventExecutor.this.run()方法。這里帮辟,雖然NioEventLoop沒(méi)有繼承Thread或者實(shí)現(xiàn)Runnable接口玩焰,不能認(rèn)為它是一個(gè)線程實(shí)現(xiàn)類。但是它有自己的run()方法荔棉,并且初次啟動(dòng)NioEventLoop時(shí)會(huì)執(zhí)行SingleThreadEventExecutor.this.run()方法蒿赢,因此暫且可以認(rèn)為它是一個(gè)線程吧。
那NioEventLoop這個(gè)名義上的線程它的run方法是什么呢壹若,如下圖店展,他就是一個(gè)死循環(huán)了秃流。說(shuō)到這里,就知道問(wèn)什么我們說(shuō)NioEventLoop是一個(gè)死循環(huán)的線程了吧概说。
Channel通道
再看到ChannelFuture f = b.bind(PORT).sync();這一句的bind方法嚣伐,它做了初始化和注冊(cè)通道的任務(wù)轩端。即ServerBootstrap初始化了注冊(cè)了通道。
他會(huì)根據(jù).channel(NioServerSocketChannel.class)這句代碼生成一個(gè)服務(wù)端的NioServerSocketChannel基茵。
Channel初始化時(shí)拱层,會(huì)給Channel塞很多東西,比如感興趣的OP_ACCEPT事件醋火,和非阻塞的配置箱吕,看到這里就明白了Netty底層還是NIO,Netty其實(shí)就是對(duì)NIO的封裝了兆旬。
還向該Channel的pipeline中加入了一個(gè)Channel初始化器ChannelInitializer丽猬,那么ChannelInitializer的initChannel什么時(shí)候執(zhí)行呢熏瞄?先劇透一下脚祟,這個(gè)方法在Channel注冊(cè)完成后執(zhí)行由桌,現(xiàn)在還在Channel初始化階段邮丰,還沒(méi)執(zhí)行。
Pipeline管道
寫(xiě)過(guò)Netty小demo的都知道娃循,我們需要處理自己的業(yè)務(wù)邏輯時(shí)都是向Channel對(duì)應(yīng)的pipeline中加入我們的ChannelHandler。這里就講一下圍繞Channel的組件吧斗蒋。
如下圖捌斧,每一個(gè)Channel都有自己所屬的EventLoop和Pipeline泉沾。
pipeline維護(hù)了鏈表形式的ChannelHandlerContext爆哑,每次addLast()添加ChannelHandler到pipeline中時(shí),都會(huì)被轉(zhuǎn)成ChannelHandlerContext并添加一個(gè)鏈表節(jié)點(diǎn)
register注冊(cè)
Channel初始化完成之后,會(huì)執(zhí)行到注冊(cè)階段柱嫌,如下代碼屯换,它提交了一個(gè)任務(wù)給到EventLoop与学,上面的分析我們知道索守,提交EventLoop.execute后抑片,會(huì)先將task入隊(duì),然后啟動(dòng)死循環(huán)執(zhí)行task截汪,這里是register0方法植捎。
那么register0就是異步通過(guò)EventLoop執(zhí)行的了焰枢。main線程繼續(xù)執(zhí)行,我們斷點(diǎn)在了NioEventLoop的線程枫匾,切換過(guò)去可以發(fā)現(xiàn)它啟動(dòng)了死循環(huán)拟淮。
調(diào)到底層進(jìn)行注冊(cè)了角虫。
接下來(lái)看到?jīng)]有委造,它要執(zhí)行ChannelInitilizer的initChannel方法了昏兆。
它是怎么執(zhí)行的呢,如下圖隶债,調(diào)用的是pipeline的execute方法跑筝。
跟蹤進(jìn)去曲梗,開(kāi)始執(zhí)行方法了妓忍。
跳到我們最初的initChannel方法世剖⊥撸可以看到最后提交了一個(gè)task到EventLoop中引颈。往pipeline中添加了一個(gè)ServerBootstrapAcceptor蝙场。bossGroup是怎么實(shí)現(xiàn)將連接建立后分配給workGroup的呢,這就是關(guān)鍵罚拟。
SocketChannel連接分配
我們知道完箩,如果有客戶端連接到服務(wù)端的話弊知,會(huì)依次執(zhí)行pipeline中的ChannelHandler,上面我們可以看到叔扼,NioServerSocketChannle最后一個(gè)ChannelHandler就是ServerBootstrapAcceptor連接分配器漫雷。
我們用客戶端連接上來(lái),發(fā)現(xiàn)它執(zhí)行到了ServerBootstrapAcceptor的channelRead方法与柑。并拿到一個(gè)SockerChannel蓄坏,向SocketChannel添加了ChannelInitializer剑辫。然后向childGroup中注冊(cè)該SocketChannel。
ChildGroup會(huì)選擇一個(gè)EventLoop來(lái)注冊(cè)這個(gè)SocketChannel椎眯。