background
netty 是一個(gè)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)通信層框架,其官方文檔的解釋為
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.
我們?cè)谛旅来笙⑼扑拖到y(tǒng)sailfish(日均推送消息量50億),新美大移動(dòng)端代理優(yōu)化系統(tǒng)shark(日均吞吐量30億)中,均選擇了netty作為底層網(wǎng)絡(luò)通信框架。
既然兩大如此重要的系統(tǒng)底層都使用到了netty,所以必然要對(duì)netty的機(jī)制,甚至源碼了若指掌,于是,便催生了netty源碼系列文章。后面,我會(huì)通過一系列的主題把我從netty源碼里所學(xué)到的毫無保留地介紹給你,源碼基于4.1.6.Final
why netty
netty底層基于jdk的NIO,我們?yōu)槭裁床恢苯踊趈dk的nio或者其他nio框架?下面是我總結(jié)出來的原因
1.使用jdk自帶的nio需要了解太多的概念,編程復(fù)雜
2.netty底層IO模型隨意切換,而這一切只需要做微小的改動(dòng)
3.netty自帶的拆包解包,異常檢測(cè)等機(jī)制讓你從nio的繁重細(xì)節(jié)中脫離出來,讓你只需要關(guān)心業(yè)務(wù)邏輯
4.netty解決了jdk的很多包括空輪訓(xùn)在內(nèi)的bug
5.netty底層對(duì)線程,selector做了很多細(xì)小的優(yōu)化,精心設(shè)計(jì)的reactor線程做到非常高效的并發(fā)處理
6.自帶各種協(xié)議棧讓你處理任何一種通用協(xié)議都幾乎不用親自動(dòng)手
7.netty社區(qū)活躍,遇到問題隨時(shí)郵件列表或者issue
8.netty已經(jīng)歷各大rpc框架,消息中間件,分布式通信中間件線上的廣泛驗(yàn)證,健壯性無比強(qiáng)大
dive into netty
了解了這么多,今天我們就從一個(gè)例子出來,開始我們的netty源碼之旅。
本篇主要講述的是netty是如何綁定端口,啟動(dòng)服務(wù)。啟動(dòng)服務(wù)的過程中,你將會(huì)了解到netty各大核心組件,我先不會(huì)細(xì)講這些組件,而是會(huì)告訴你各大組件是怎么串起來組成netty的核心
example
下面是一個(gè)非常簡單的服務(wù)端啟動(dòng)代碼
public final class SimpleServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new SimpleServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
}
}
簡單的幾行代碼就能開啟一個(gè)服務(wù)端,端口綁定在8888,使用nio模式,下面講下每一個(gè)步驟的處理細(xì)節(jié)
EventLoopGroup
已經(jīng)在我的其他文章中詳細(xì)剖析過,說白了,就是一個(gè)死循環(huán),不停地檢測(cè)IO事件,處理IO事件,執(zhí)行任務(wù)
ServerBootstrap
是服務(wù)端的一個(gè)啟動(dòng)輔助類,通過給他設(shè)置一系列參數(shù)來綁定端口啟動(dòng)服務(wù)
group(bossGroup, workerGroup)
我們需要兩種類型的人干活,一個(gè)是老板,一個(gè)是工人,老板負(fù)責(zé)從外面接活,接到的活分配給工人干,放到這里,bossGroup
的作用就是不斷地accept到新的連接,將新的連接丟給workerGroup
來處理
.channel(NioServerSocketChannel.class)
表示服務(wù)端啟動(dòng)的是nio相關(guān)的channel,channel在netty里面是一大核心概念,可以理解為一條channel就是一個(gè)連接或者一個(gè)服務(wù)端bind動(dòng)作,后面會(huì)細(xì)說
.handler(new SimpleServerHandler()
表示服務(wù)器啟動(dòng)過程中,需要經(jīng)過哪些流程,這里SimpleServerHandler
最終的頂層接口為ChannelHander
,是netty的一大核心概念,表示數(shù)據(jù)流經(jīng)過的處理器,可以理解為流水線上的每一道關(guān)卡
childHandler(new ChannelInitializer<SocketChannel>)...
表示一條新的連接進(jìn)來之后,該怎么處理,也就是上面所說的,老板如何給工人配活
ChannelFuture f = b.bind(8888).sync();
這里就是真正的啟動(dòng)過程了,綁定8888端口,等待服務(wù)器啟動(dòng)完畢,才會(huì)進(jìn)入下行代碼
f.channel().closeFuture().sync();
等待服務(wù)端關(guān)閉socket
bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();
關(guān)閉兩組死循環(huán)
上述代碼可以很輕松地再本地跑起來,最終控制臺(tái)的輸出為:
handlerAdded
channelRegistered
channelActive
關(guān)于為什么會(huì)順序輸出這些,深入分析之后其實(shí)很easy
深入細(xì)節(jié)
ServerBootstrap
一系列的參數(shù)配置其實(shí)沒啥好講的,無非就是使用method chaining的方式將啟動(dòng)服務(wù)器需要的參數(shù)保存到filed。我們的重點(diǎn)落入到下面這段代碼
b.bind(8888).sync();
這里說一句:我們剛開始看源碼,對(duì)細(xì)節(jié)沒那么清楚的情況下可以借助IDE的debug功能,step by step,one step one test或者二分test的方式,來確定哪行代碼是最終啟動(dòng)服務(wù)的入口,在這里,我們已經(jīng)確定了bind方法是入口,我們跟進(jìn)去,分析
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
通過端口號(hào)創(chuàng)建一個(gè) InetSocketAddress
,然后繼續(xù)bind
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
validate()
驗(yàn)證服務(wù)啟動(dòng)需要的必要參數(shù),然后調(diào)用doBind()
private ChannelFuture doBind(final SocketAddress localAddress) {
//...
final ChannelFuture regFuture = initAndRegister();
//...
final Channel channel = regFuture.channel();
//...
doBind0(regFuture, channel, localAddress, promise);
//...
return promise;
}
這里,我去掉了細(xì)枝末節(jié),讓我們專注于核心方法,其實(shí)就兩大核心一個(gè)是 initAndRegister()
,以及doBind0()
其實(shí),從方法名上面我們已經(jīng)可以略窺一二,init->初始化,register->注冊(cè),那么到底要注冊(cè)到什么呢?聯(lián)系到nio里面輪詢器的注冊(cè),可能是把某個(gè)東西初始化好了之后注冊(cè)到selector上面去,最后bind,像是在本地綁定端口號(hào),帶著這些猜測(cè),我們深入下去
initAndRegister()
final ChannelFuture initAndRegister() {
Channel channel = null;
// ...
channel = channelFactory.newChannel();
//...
init(channel);
//...
ChannelFuture regFuture = config().group().register(channel);
//...
return regFuture;
}
我們還是專注于核心代碼,拋開邊角料,我們看到 initAndRegister()
做了幾件事情
1.new一個(gè)channel
2.init這個(gè)channel
3.將這個(gè)channel register到某個(gè)對(duì)象
我們逐步分析這三件事情
1.new一個(gè)channel
我們首先要搞懂channel的定義,netty官方對(duì)channel的描述如下
A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind
這里的channel,由于是在服務(wù)啟動(dòng)的時(shí)候創(chuàng)建,我們可以和普通Socket編程中的ServerSocket對(duì)應(yīng)上,表示服務(wù)端綁定的時(shí)候經(jīng)過的一條流水線
我們發(fā)現(xiàn)這條channel是通過一個(gè) channelFactory
new出來的,channelFactory
的接口很簡單
public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
/**
* Creates a new channel.
*/
@Override
T newChannel();
}
就一個(gè)方法,我們查看channelFactory被賦值的地方
AbstractBootstrap.java
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) {
throw new NullPointerException("channelFactory");
}
if (this.channelFactory != null) {
throw new IllegalStateException("channelFactory set already");
}
this.channelFactory = channelFactory;
return (B) this;
}
在這里被賦值,我們層層回溯,查看該函數(shù)被調(diào)用的地方,發(fā)現(xiàn)最終是在這個(gè)函數(shù)中,ChannelFactory被new出
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
這里,我們的demo程序調(diào)用channel(channelClass)
方法的時(shí)候,將channelClass
作為ReflectiveChannelFactory
的構(gòu)造函數(shù)創(chuàng)建出一個(gè)ReflectiveChannelFactory
demo端的代碼如下:
.channel(NioServerSocketChannel.class);
然后回到本節(jié)最開始
channelFactory.newChannel();
我們就可以推斷出,最終是調(diào)用到 ReflectiveChannelFactory.newChannel()
方法,跟進(jìn)
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
}
看到clazz.newInstance();
,我們明白了,原來是通過反射的方式來創(chuàng)建一個(gè)對(duì)象,而這個(gè)class就是我們?cè)?code>ServerBootstrap中傳入的NioServerSocketChannel.class
結(jié)果,繞了一圈,最終創(chuàng)建channel相當(dāng)于調(diào)用默認(rèn)構(gòu)造函數(shù)new出一個(gè) NioServerSocketChannel
對(duì)象
這里提一下,讀源碼細(xì)節(jié),有兩種讀的方式,一種是回溯,比如用到某個(gè)對(duì)象的時(shí)候可以逐層追溯,一定會(huì)找到該對(duì)象的最開始被創(chuàng)建的代碼區(qū)塊,還有一種方式就是自頂向下,逐層分析,一般用在分析某個(gè)具體的方法,庖丁解牛,最后拼接出完整的流程
接下來我們就可以將重心放到 NioServerSocketChannel
的默認(rèn)構(gòu)造函數(shù)
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
//...
return provider.openServerSocketChannel();
}
通過SelectorProvider.openServerSocketChannel()
創(chuàng)建一條server端channel,然后進(jìn)入到以下方法
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
這里第一行代碼就跑到父類里面去了,第二行,new出來一個(gè) NioServerSocketChannelConfig
,其頂層接口為 ChannelConfig
,netty官方的描述如下
A set of configuration properties of a Channel.
基本可以判定,ChannelConfig
也是netty里面的一大核心模塊,初次看源碼,看到這里,我們大可不必深挖這個(gè)對(duì)象,而是在用到的時(shí)候再回來深究,只要記住,這個(gè)對(duì)象在創(chuàng)建NioServerSocketChannel
對(duì)象的時(shí)候被創(chuàng)建即可
我們繼續(xù)追蹤到 NioServerSocketChannel
的父類
AbstractNioMessageChannel.java
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
繼續(xù)往上追
AbstractNioChannel.java
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
//...
ch.configureBlocking(false);
//...
}
這里,簡單地將前面 provider.openServerSocketChannel();
創(chuàng)建出來的 ServerSocketChannel
保存到成員變量,然后調(diào)用ch.configureBlocking(false);
設(shè)置該channel為非阻塞模式,標(biāo)準(zhǔn)的jdk nio編程的玩法
這里的 readInterestOp
即前面層層傳入的 SelectionKey.OP_ACCEPT
,接下來重點(diǎn)分析 super(parent);
(這里的parent其實(shí)是null,由前面寫死傳入)
AbstractChannel.java
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
到了這里,又new出來三大組件,賦值到成員變量,分別為
id = newId();
protected ChannelId newId() {
return DefaultChannelId.newInstance();
}
id是netty中每條channel的唯一標(biāo)識(shí),這里不細(xì)展開,接著
unsafe = newUnsafe();
protected abstract AbstractUnsafe newUnsafe();
查看Unsafe的定義
Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread
成功捕捉netty的又一大組件,我們可以先不用管TA是干嘛的,只需要知道這里的 newUnsafe
方法最終屬于類NioServerSocketChannel
中
最后
pipeline = newChannelPipeline();
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
初次看這段代碼,可能并不知道 DefaultChannelPipeline
是干嘛用的,我們?nèi)匀皇褂蒙厦娴姆绞?,查看頂層接?code>ChannelPipeline的定義
A list of ChannelHandlers which handles or intercepts inbound events and outbound operations of a Channel
從該類的文檔中可以看出,該接口基本上又是netty的一大核心模塊
到了這里,我們總算把一個(gè)服務(wù)端channel創(chuàng)建完畢了,將這些細(xì)節(jié)串起來的時(shí)候,我們順帶提取出netty的幾大基本組件,先總結(jié)如下
- Channel
- ChannelConfig
- ChannelId
- Unsafe
- Pipeline
- ChannelHander
初次看代碼的時(shí)候,我們的目標(biāo)是跟到服務(wù)器啟動(dòng)的那一行代碼,我們先把以上這幾個(gè)組件記下來,等代碼跟完,我們就可以自頂向下,逐層分析,我會(huì)放到后面源碼系列中去深入到每個(gè)組件
總結(jié)一下,用戶調(diào)用方法 Bootstrap.bind(port)
第一步就是通過反射的方式new一個(gè)NioServerSocketChannel
對(duì)象,并且在new的過程中創(chuàng)建了一系列的核心組件,僅此而已,并無他,真正的啟動(dòng)我們還需要繼續(xù)跟
2.init這個(gè)channel
到了這里,你最好跳到文章最開始的地方回憶一下,第一步newChannel完畢,這里就對(duì)這個(gè)channel做init,init方法具體干啥,我們深入
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
初次看到這個(gè)方法,可能會(huì)覺得,哇塞,老長了,這可這么看?還記得我們前面所說的嗎,庖丁解牛,逐步拆解,最后歸一,下面是我的拆解步驟
1.設(shè)置option和attr
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
通過這里我們可以看到,這里先調(diào)用options0()
以及attrs0()
,然后將得到的options和attrs注入到channelConfig或者channel中,關(guān)于option和attr是干嘛用的,其實(shí)你現(xiàn)在不用了解得那么深入,只需要查看最頂層接口ChannelOption
以及查看一下channel的具體繼承關(guān)系,就可以了解,我把這兩個(gè)也放到后面的源碼分析系列再講
2.設(shè)置新接入channel的option和attr
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
這里,和上面類似,只不過不是設(shè)置當(dāng)前channel的這兩個(gè)屬性,而是對(duì)應(yīng)到新進(jìn)來連接對(duì)應(yīng)的channel,由于我們這篇文章只關(guān)心到server如何啟動(dòng),接入連接放到下一篇文章中詳細(xì)剖析
3.加入新連接處理器
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
到了最后一步,p.addLast()
向serverChannel的流水線處理器中加入了一個(gè) ServerBootstrapAcceptor
,從名字上就可以看出來,這是一個(gè)接入器,專門接受新請(qǐng)求,把新的請(qǐng)求扔給某個(gè)事件循環(huán)器,我們先不做過多分析
來,我們總結(jié)一下,我們發(fā)現(xiàn)其實(shí)init也沒有啟動(dòng)服務(wù),只是初始化了一些基本的配置和屬性,以及在pipeline上加入了一個(gè)接入器,用來專門接受新連接,我們還得繼續(xù)往下跟
3.將這個(gè)channel register到某個(gè)對(duì)象
這一步,我們是分析如下方法
ChannelFuture regFuture = config().group().register(channel);
調(diào)用到 NioEventLoop
中的register
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
好了,到了這一步,還記得這里的unsafe()
返回的應(yīng)該是什么對(duì)象嗎?不記得的話可以看下前面關(guān)于unsafe的描述,或者最快的方式就是debug到這邊,跟到register方法里面,看看是哪種類型的unsafe
我們跟進(jìn)去之后發(fā)現(xiàn)是
AbstractUnsafe.java
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// ...
AbstractChannel.this.eventLoop = eventLoop;
// ...
register0(promise);
}
這里我們依然只需要focus重點(diǎn),先將EventLoop事件循環(huán)器綁定到該NioServerSocketChannel上,然后調(diào)用 register0()
private void register0(ChannelPromise promise) {
try {
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
這一段其實(shí)也很清晰,先調(diào)用 doRegister();
,具體干啥待會(huì)再講,然后調(diào)用invokeHandlerAddedIfNeeded()
, 于是乎,控制臺(tái)第一行打印出來的就是
handlerAdded
關(guān)于最終是如何調(diào)用到的,我們后面詳細(xì)剖析pipeline的時(shí)候再講
然后調(diào)用 pipeline.fireChannelRegistered();
調(diào)用之后,控制臺(tái)的顯示為
handlerAdded
channelRegistered
繼續(xù)往下跟
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
讀到這,你可能會(huì)想當(dāng)然地以為,控制臺(tái)最后一行
pipeline.fireChannelActive();
由這行代碼輸出,我們不妨先看一下 isActive()
方法
@Override
public boolean isActive() {
return javaChannel().socket().isBound();
}
最終調(diào)用到j(luò)dk中
ServerSocket.java
/**
* Returns the binding state of the ServerSocket.
*
* @return true if the ServerSocket succesfuly bound to an address
* @since 1.4
*/
public boolean isBound() {
// Before 1.3 ServerSockets were always bound during creation
return bound || oldImpl;
}
這里isBound()
返回false,但是從目前我們跟下來的流程看,我們并沒有將一個(gè)ServerSocket綁定到一個(gè)address,所以 isActive()
返回false,我們沒有成功進(jìn)入到pipeline.fireChannelActive();
方法,那么最后一行到底是誰輸出的呢,我們有點(diǎn)抓狂,其實(shí),只要熟練運(yùn)用IDE,要定位函數(shù)調(diào)用棧,無比簡單
下面是我用intellij定位函數(shù)調(diào)用的具體方法
我們先在最終輸出文字的這一行代碼處打一個(gè)斷點(diǎn),然后debug,運(yùn)行到這一行,intellij自動(dòng)給我們拉起了調(diào)用棧,我們唯一要做的事,就是移動(dòng)方向鍵,就能看到函數(shù)的完整的調(diào)用鏈
如果你看到方法的最近的發(fā)起端是一個(gè)線程Runnable的run方法,那么就在提交Runnable對(duì)象方法的地方打一個(gè)斷點(diǎn),去掉其他斷點(diǎn),重新debug,比如我們首次debug發(fā)現(xiàn)調(diào)用棧中的最近的一個(gè)Runnable如下
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
我們停在了這一行pipeline.fireChannelActive();
, 我們想看最初始的調(diào)用,就得跳出來,斷點(diǎn)打到 if (!wasActive && isActive())
,因?yàn)閚etty里面很多任務(wù)執(zhí)行都是異步線程即reactor線程調(diào)用的(具體可以看reactor線程三部曲中的最后一曲),如果我們要查看最先發(fā)起的方法調(diào)用,我們必須得查看Runnable被提交的地方,逐次遞歸下去,就能找到那行"消失的代碼"
最終,通過這種方式,終于找到了 pipeline.fireChannelActive();
的發(fā)起調(diào)用的代碼,不巧,剛好就是下面的doBind0()
方法
doBind0()
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
我們發(fā)現(xiàn),在調(diào)用doBind0(...)
方法的時(shí)候,是通過包裝一個(gè)Runnable進(jìn)行異步化的,關(guān)于異步化task,可以看下我前面的文章,netty源碼分析之揭開reactor線程的面紗(三)
好,接下來我們進(jìn)入到channel.bind()
方法
AbstractChannel.java
@Override
public ChannelFuture bind(SocketAddress localAddress) {
return pipeline.bind(localAddress);
}
發(fā)現(xiàn)是調(diào)用pipeline的bind方法
@Override
public final ChannelFuture bind(SocketAddress localAddress) {
return tail.bind(localAddress);
}
相信你對(duì)tail是什么不是很了解,可以翻到最開始,tail在創(chuàng)建pipeline的時(shí)候出現(xiàn)過,關(guān)于pipeline和tail對(duì)應(yīng)的類,我后面源碼系列會(huì)詳細(xì)解說,這里,你要想知道接下來代碼的走向,唯一一個(gè)比較好的方式就是debug 單步進(jìn)入,篇幅原因,我就不詳細(xì)展開
最后,我們來到了如下區(qū)域
HeadContext.java
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
這里的unsafe就是前面提到的 AbstractUnsafe
, 準(zhǔn)確點(diǎn),應(yīng)該是 NioMessageUnsafe
我們進(jìn)入到它的bind方法
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// ...
boolean wasActive = isActive();
// ...
doBind(localAddress);
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
顯然按照正常流程,我們前面已經(jīng)分析到 isActive();
方法返回false,進(jìn)入到 doBind()
之后,如果channel被激活了,就發(fā)起pipeline.fireChannelActive();
調(diào)用,最終調(diào)用到用戶方法,在控制臺(tái)打印出了最后一行,所以到了這里,你應(yīng)該清楚為什么最終會(huì)在控制臺(tái)按順序打印出那三行字了吧
doBind()
方法也很簡單
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
//noinspection Since15
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
最終調(diào)到了jdk里面的bind方法,這行代碼過后,正常情況下,就真正進(jìn)行了端口的綁定。
另外,通過自頂向下的方式分析,在調(diào)用pipeline.fireChannelActive();
方法的時(shí)候,會(huì)調(diào)用到如下方法
HeadContext.java
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
進(jìn)入 readIfIsAutoRead
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
分析isAutoRead
方法
private volatile int autoRead = 1;
public boolean isAutoRead() {
return autoRead == 1;
}
由此可見,isAutoRead
方法默認(rèn)返回true,于是進(jìn)入到以下方法
public Channel read() {
pipeline.read();
return this;
}
最終調(diào)用到
AbstractNioUnsafe.java
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
這里的this.selectionKey
就是我們?cè)谇懊鎟egister步驟返回的對(duì)象,前面我們?cè)趓egister的時(shí)候,注冊(cè)測(cè)ops是0
回憶一下注冊(cè)
AbstractNioChannel
selectionKey = javaChannel().register(eventLoop().selector, 0, this)
這里相當(dāng)于把注冊(cè)過的ops取出來,通過了if條件,然后調(diào)用
selectionKey.interestOps(interestOps | readInterestOp);
而這里的 readInterestOp
就是前面newChannel的時(shí)候傳入的SelectionKey.OP_ACCEPT
,又是標(biāo)準(zhǔn)的jdk nio的玩法,到此,你需要了解的細(xì)節(jié)基本已經(jīng)差不多了,就這樣結(jié)束吧!
summary
最后,我們來做下總結(jié),netty啟動(dòng)一個(gè)服務(wù)所經(jīng)過的流程
1.設(shè)置啟動(dòng)類參數(shù),最重要的就是設(shè)置channel
2.創(chuàng)建server對(duì)應(yīng)的channel,創(chuàng)建各大組件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等
3.初始化server對(duì)應(yīng)的channel,設(shè)置一些attr,option,以及設(shè)置子channel的attr,option,給server的channel添加新channel接入器,并出發(fā)addHandler,register等事件
4.調(diào)用到j(luò)dk底層做端口綁定,并觸發(fā)active事件,active觸發(fā)的時(shí)候,真正做服務(wù)端口綁定
另外,文章中閱讀源碼的思路詳細(xì)或許也可以給你帶來一些幫助。
如果你想從零到一深入學(xué)習(xí) Netty,可以加我微信(備注:簡書專享)。
贈(zèng)送一份目前正在掘金售賣的小冊(cè)一份