原文出處http://cmsblogs.com/ 『chenssy』
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!
上篇博客(【死磕Netty】----Netty的核心組件及其設(shè)計),了解了 Netty 的核心組件及其設(shè)計,但是這些都是零散的,不成體系。那么 Netty 是如何利用這些組件構(gòu)建成一個高性能的異步通信框架。通過這篇博客可以初步了解。
下面先來一段 Netty 服務端的代碼:
public class NettyServer {
public void bind(int port){
// 創(chuàng)建EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(); //創(chuàng)建BOSS線程組 用于服務端接受客戶端的連接
EventLoopGroup workerGroup = new NioEventLoopGroup(); //創(chuàng)建WORK線程組 用于進行SocketChannel的網(wǎng)絡(luò)讀寫
try {
// 創(chuàng)建ServerBootStrap實例
// ServerBootstrap 用于啟動NIO服務端的輔助啟動類,目的是降低服務端的開發(fā)復雜度
ServerBootstrap b = new ServerBootstrap();
// 綁定Reactor線程池
b.group(bossGroup, workerGroup)
// 設(shè)置并綁定服務端Channel
// 指定所使用的NIO傳輸?shù)腃hannel
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingServerHandler())
.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(Channel ch) throws Exception {
//do something
}
});
// 綁定端口,同步等待成功
ChannelFuture future = b.bind(port).sync();
// 等待服務端監(jiān)聽端口關(guān)閉
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 優(yōu)雅地關(guān)閉
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class LoggingServerHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("loggin-channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("loggin-channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("loggin-handlerAdded");
}
}
public static void main(String[] args){
new NettyServer().bind(8899);
}
}
上面代碼為 Netty 服務器端的完整代碼,在整個服務端代碼中會涉及如下幾個核心類。
ServerBootstrap
ServerBootstrap 為 Netty 服務端的啟動輔助類,它提供了一系列的方法用于設(shè)置服務端啟動相關(guān)的參數(shù)。
Channel
Channel 為 Netty 網(wǎng)絡(luò)操作抽象類,它定義了一組功能,其提供的 API 大大降低了直接使用 Socket 類的復雜性。當然它也不僅僅只是包括了網(wǎng)絡(luò) IO 操作的基本功能,還包括一些與 Netty 框架相關(guān)的功能,包括獲取該 Channel 的 EventLoop 等等。
EventLoopGroup
EventLoopGroup 為 Netty 的 Reactor 線程池,它實際上就是 EventLoop 的容器,而 EventLoop 為 Netty 的核心抽象類,它的主要職責是處理所有注冊到本線程多路復用器 Selector 上的 Channel。
ChannelHandler
ChannelHandler 作為 Netty 的主要組件,它主要負責 I/O 事件或者 I/O 操作進行攔截和處理,它可以選擇性地攔截和處理自己感覺興趣的事件,也可以透傳和終止事件的傳遞。
ChannelPipeline
ChannelPipeline 是 ChannelHandler 鏈的容器,它負責 ChannelHandler 的管理和事件攔截與調(diào)度。每當新建一個 Channel 都會分配一個新的 ChannelPepeline,同時這種關(guān)聯(lián)是永久性的。
以上是簡要介紹,詳細介紹請參考(【死磕Netty】-----Netty的核心組件及其設(shè)計)
服務端創(chuàng)建流程
Netty 服務端創(chuàng)建的時序圖,如下(摘自《Netty權(quán)威指南(第二版)》)
主要步驟為:
- 創(chuàng)建 ServerBootstrap 實例
- 設(shè)置并綁定 Reactor 線程池
- 設(shè)置并綁定服務端 Channel
- 創(chuàng)建并初始化 ChannelPipeline
- 添加并設(shè)置 ChannelHandler
- 綁定并啟動監(jiān)聽端口
服務端源碼分析
1、創(chuàng)建兩個EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
bossGroup 為 BOSS 線程組,用于服務端接受客戶端的連接, workerGroup 為 worker 線程組,用于進行 SocketChannel 的網(wǎng)絡(luò)讀寫。當然也可以創(chuàng)建一個并共享。
2、創(chuàng)建ServerBootstrap實例
ServerBootstrap b = new ServerBootstrap();
ServerBootStrap為Netty服務端的啟動引導類,用于幫助用戶快速配置、啟動服務端服務。提供的方法如下:
方法名稱 | 方法描述 |
---|---|
group |
設(shè)置 ServerBootstrap 要用的 EventLoopGroup |
channel |
設(shè)置將要被實例化的 ServerChannel 類 |
option |
實例化的 ServerChannel 的配置項 |
childHandler |
設(shè)置并添加 ChannelHandler |
bind |
綁定 ServerChannel |
ServerBootStrap底層采用裝飾者模式。
關(guān)于 ServerBootStrap 我們后續(xù)做詳細分析。
3、設(shè)置并綁定Reactor線程池
調(diào)用 group()
方法,為 ServerBootstrap 實例設(shè)置并綁定 Reactor 線程池。
b.group(bossGroup, workerGroup)
EventLoopGroup 為 Netty 線程池,它實際上就是 EventLoop 的數(shù)組容器。EventLoop 的職責是處理所有注冊到本線程多路復用器 Selector 上的 Channel,Selector 的輪詢操作由綁定的 EventLoop 線程 run 方法驅(qū)動,在一個循環(huán)體內(nèi)循環(huán)執(zhí)行。通俗點講就是一個死循環(huán),不斷的檢測 I/O 事件、處理 I/O 事件。
這里設(shè)置了兩個group,這個其實有點兒像我們工作一樣。需要兩類型的工人,一個老板(bossGroup),一個工人(workerGroup),老板負責從外面接活,工人則負責死命干活(尼瑪,和我上家公司一模一樣)。所以這里 bossGroup 的作用就是不斷地接收新的連接,接收之后就丟給 workerGroup 來處理,workerGroup 負責干活就行(負責客戶端連接的 IO 操作)。
源碼如下:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup); // 綁定boosGroup
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup; // 綁定workerGroup
return this;
}
其中父 EventLoopGroup 傳遞到父類的構(gòu)造函數(shù)中:
public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
}
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return (B) this;
}
4、設(shè)置并綁定服務端Channel
綁定線程池后,則需要設(shè)置 channel 類型,服務端用的是 NioServerSocketChannel 。
.channel(NioServerSocketChannel.class)
調(diào)用 ServerBootstrap.channel
方法用于設(shè)置服務端使用的 Channel,傳遞一個 NioServerSocketChannel Class對象,Netty通過工廠類,利用反射創(chuàng)建NioServerSocketChannel 對象,如下:
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
channelFactory()
用于設(shè)置 Channel 工廠的:
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return channelFactory((ChannelFactory<C>) channelFactory);
}
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;
}
這里傳遞的是 ReflectiveChannelFactory,其源代碼如下:
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;
}
//需要創(chuàng)建 channel 的時候,該方法將被調(diào)用
@Override
public T newChannel() {
try {
// 反射創(chuàng)建對應 channel
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(clazz) + ".class";
}
}
確定服務端的 Channel(NioServerSocketChannel)后,調(diào)用 option()
方法設(shè)置 Channel 參數(shù),作為服務端,主要是設(shè)置TCP的backlog參數(shù),如下:
.option(ChannelOption.SO_BACKLOG, 1024)
option()
源碼如下:
public <T> B option(ChannelOption<T> option, T value) {
if (option == null) {
throw new NullPointerException("option");
}
if (value == null) {
synchronized (options) {
options.remove(option);
}
} else {
synchronized (options) {
options.put(option, value);
}
}
return (B) this;
}
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
五、添加并設(shè)置ChannelHandler
設(shè)置完 Channel 參數(shù)后,用戶可以為啟動輔助類和其父類分別指定 Handler。
.handler(new LoggingServerHandler())
.childHandler(new ChannelInitializer(){
//省略代碼
})
這兩個 Handler 不一樣,前者(handler()
)設(shè)置的 Handler 是服務端 NioServerSocketChannel的,后者(childHandler()
)設(shè)置的 Handler 是屬于每一個新建的 NioSocketChannel 的。跟蹤源代碼會發(fā)現(xiàn)兩種所處的類不一樣,handler 位于 AbstractBootstrap 中,childHandler 位于 ServerBootstrap 中,如下:
// AbstractBootstrap
public B handler(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
return (B) this;
}
// ServerBootstrap
public ServerBootstrap childHandler(ChannelHandler childHandler) {
if (childHandler == null) {
throw new NullPointerException("childHandler");
}
this.childHandler = childHandler;
return this;
}
ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有連接該監(jiān)聽端口的客戶端都會執(zhí)行它,父類 AbstractBootstrap 中的 Handler 是一個工廠類,它為每一個新接入的客戶端都創(chuàng)建一個新的 Handler。如下圖(《Netty權(quán)威指南(第二版)》):
[圖片上傳失敗...(image-bcab2c-1512393901237)]
六、綁定端口,啟動服務
服務端最后一步,綁定端口并啟動服務,如下:
ChannelFuture future = b.bind(port).sync();
調(diào)用 ServerBootstrap 的 bind()
方法進行端口綁定:
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
首先調(diào)用 validate()
方法進行參數(shù)校驗,然后調(diào)用 doBind()
方法:
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化并注冊一個Channel
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// 注冊成功
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
// 調(diào)用doBind0綁定
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final AbstractBootstrap.PendingRegistrationPromise promise = new AbstractBootstrap.PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
該方法涉及內(nèi)容較多,我們分解來看,如下:
- 首先通過
initAndRegister()
得到一個 ChannelFuture 對象 regFuture; - 根據(jù)得到的 regFuture 對象判斷該對象是否拋出異常 (
regFuture.cause()
),如果是,直接返回; - 根據(jù)
regFuture.isDone()
判斷initAndRegister()
是否執(zhí)行完畢,如果執(zhí)行完成,則調(diào)用doBind0
; - 若
initAndRegister()
沒有執(zhí)行完畢,則向 regFuture 對象添加一個 ChannelFutureListener 監(jiān)聽,當initAndRegister()
執(zhí)行完畢后會調(diào)用operationComplete()
,在operationComplete()
中依然會判斷 ChannelFuture 是否拋出異常,如果沒有則調(diào)用doBind0
進行綁定。
按照上面的步驟我們一步一步來剖析 doBind()
方法。
initAndRegister()
執(zhí)行 initAndRegister()
會得到一個 ChannelFuture 對象 regFuture,代碼如下:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 新建一個Channel
channel = channelFactory.newChannel();
// 初始化Channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
}
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// /向EventLoopGroup中注冊一個channel
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
首先調(diào)用 newChannel()
新建一個Channel,這里是NioServerSocketChannel,還記前面 4、設(shè)置并綁定服務端Channel(.channel(NioServerSocketChannel.class)
)中 設(shè)置的Channel工廠類么?在這里派上用處了。在上面提到了通過反射的機制我們可以得到一個 NioServerSocketChannel 類的實例。那么 NioServerSocketChannel 到底是一個什么東西呢?如下圖:
上圖是 NioServerSocketChannel 的繼承體系結(jié)構(gòu)圖, NioServerSocketChannel 在構(gòu)造函數(shù)中會依靠父類來完成一項一項的初始化工作。先看 NioServerSocketChannel 構(gòu)造函數(shù)。
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
newSocket()
方法較為簡單,它是利用 SelectorProvider.openServerSocketChannel()
,產(chǎn)生一個 ServerSocketChannel 對象。
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
該構(gòu)造函數(shù)首先是調(diào)用父類的構(gòu)造方法,然后設(shè)置 config屬性。父類構(gòu)造方法如下:
// AbstractNioMessageChannel
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
// AbstractNioChannel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
// AbstractChannel
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
通過 super()
,一層一層往上,直到 AbstractChannel。我們從最上層解析。
- AbstractChannel 設(shè)置了 unsafe (
unsafe = newUnsafe()
)和 pipeline(pipeline = newChannelPipeline()
); - AbstractNioChannel 將當前 ServerSocketChannel 設(shè)置成了非阻塞(
ch.configureBlocking(false);
),同時設(shè)置SelectionKey.OP_ACCEPT事件(this.readInterestOp = readInterestOp;
readInterestOp 值由 NioServerSocketChannel 中傳遞); - NioServerSocketChannel 設(shè)置 config屬性(
config = new NioServerSocketChannelConfig(this, javaChannel().socket())
)。
所以
channel = channelFactory.newChannel()
通過反射機制產(chǎn)生了 NioServerSocketChannel 類實例。同時該實例設(shè)置了NioMessageUnsafe、DefaultChannelPipeline、非阻塞、SelectionKey.OP_ACCEPT事件 和 NioServerSocketChannelConfig 屬性。
看完了 channelFactory.newChannel();
,我們再看 init()
。
void init(Channel channel) throws Exception {
// 設(shè)置配置的option參數(shù)
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());
}
}
// 獲取綁定的pipeline
ChannelPipeline p = channel.pipeline();
// 準備child用到的4個part
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()));
}
// 為NioServerSocketChannel的pipeline添加一個初始化Handler,
// 當NioServerSocketChannel在EventLoop注冊成功時,該handler的init方法將被調(diào)用
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
//如果用戶配置過Handler
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// 為NioServerSocketChannel的pipeline添加ServerBootstrapAcceptor處理器
// 該Handler主要用來將新創(chuàng)建的NioSocketChannel注冊到EventLoopGroup中
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
其實整個過程可以分為三個步驟:
- 設(shè)置 Channel 的 option 和 attr;
- 獲取綁定的 pipeline,然后為 NioServerSocketChanne l綁定的 pipeline 添加 Handler;
- 將用于服務端注冊的 Handler ServerBootstrapAcceptor 添加到 ChannelPipeline 中。ServerBootstrapAcceptor 為一個接入器,專門接受新請求,把新的請求扔給某個事件循環(huán)器。
至此初始化部分已經(jīng)結(jié)束,我們再看注冊部分,
// /向EventLoopGroup中注冊一個channel
ChannelFuture regFuture = config().group().register(channel);
注冊方法的調(diào)用位于 initAndRegister()
方法中。注意這里的 group()
返回的是前面的 boss NioEvenLoopGroup,它繼承 MultithreadEventLoopGroup,調(diào)用的 register()
,也是 MultithreadEventLoopGroup 中的。如下:
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
調(diào)用 next()
方法從 EventLoopGroup 中獲取下一個 EventLoop,調(diào)用 register()
方法注冊:
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
將Channel和EventLoop封裝成一個DefaultChannelPromise對象,然后調(diào)用register()方法。DefaultChannelPromis為ChannelPromise的默認實現(xiàn),而ChannelPromisee繼承Future,具備異步執(zhí)行結(jié)構(gòu),綁定Channel,所以又具備了監(jiān)聽的能力,故而ChannelPromis是Netty異步執(zhí)行的核心接口。
public ChannelFuture register(ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
首先獲取 channel 的 unsafe 對象,該 unsafe 對象就是在之前設(shè)置過得。然后調(diào)用 register()
方法,如下:
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
// 必須要保證注冊是由該EventLoop發(fā)起的
if (eventLoop.inEventLoop()) {
register0(promise); // 注冊
} else {
// 如果不是單獨封裝成一個task異步執(zhí)行
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
過程如下:
- 首先通過
isRegistered()
判斷該 Channel 是否已經(jīng)注冊到 EventLoop 中; - 通過
eventLoop.inEventLoop()
來判斷當前線程是否為該 EventLoop 自身發(fā)起的,如果是,則調(diào)用register0()
直接注冊; - 如果不是,說明該 EventLoop 中的線程此時沒有執(zhí)行權(quán),則需要新建一個線程,單獨封裝一個 Task,而該 Task 的主要任務則是執(zhí)行
register0()
。
無論當前 EventLoop 的線程是否擁有執(zhí)行權(quán),最終都會要執(zhí)行 register0()
,如下:
private void register0(ChannelPromise promise) {
try {
// 確保 Channel 處于 open
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 真正的注冊動作
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise); //設(shè)置注冊結(jié)果為成功
pipeline.fireChannelRegistered();
if (isActive()) {
//如果是首次注冊,發(fā)起 pipeline 的 fireChannelActive
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
如果 Channel 處于 open 狀態(tài),則調(diào)用 doRegister()
方法完成注冊,然后將注冊結(jié)果設(shè)置為成功。最后判斷如果是首次注冊且處于激活狀態(tài),則發(fā)起 pipeline 的 fireChannelActive()
。
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 注冊到NIOEventLoop的Selector上
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
這里注冊時 ops 設(shè)置的是 0,也就是說 ServerSocketChannel 僅僅只是表示了注冊成功,還不能監(jiān)聽任何網(wǎng)絡(luò)操作,這樣做的目的是(摘自《Netty權(quán)威指南(第二版)》):
- 注冊方式是多態(tài)的,它既可以被 NIOServerSocketChannel 用來監(jiān)聽客戶端的連接接入,也可以注冊 SocketChannel 用來監(jiān)聽網(wǎng)絡(luò)讀或者寫操作。
- 通過
SelectionKey.interestOps(int ops)
方法可以方便地修改監(jiān)聽操作位。所以,此處注冊需要獲取 SelectionKey 并給 AbstractNIOChannel 的成員變量 selectionKey 賦值。
由于這里 ops 設(shè)置為 0,所以還不能監(jiān)聽讀寫事件。調(diào)用 doRegister()
后,然后調(diào)用pipeline.invokeHandlerAddedIfNeeded();
,這個時候控制臺會出現(xiàn) loggin-handlerAdded
,內(nèi)部如何調(diào)用,我們在剖析 pipeline 時再做詳細分析。然后將注冊結(jié)果設(shè)置為成功(safeSetSuccess(promise)
)。調(diào)用 pipeline.fireChannelRegistered();
這個時候控制臺會打印 loggin-channelRegistered
。這里簡單分析下該方法。
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
pipeline 維護著 handle 鏈表,事件會在 NioServerSocketChannel 的 pipeline 中傳播。最終都會調(diào)用 next.invokeChannelRegistered()
,如下:
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
在 invokeChannelRegistered()
會調(diào)用我們在前面設(shè)置的 handler (還記得簽名的 handler(new LoggingServerHandler()
)么)的 channelRegistered()
,這個時候控制臺應該會打印 loggin-channelRegistered
。
到這里initAndRegister() (final ChannelFuture regFuture = initAndRegister();)
就分析完畢了,該方法主要做如下三件事:
- 通過反射產(chǎn)生了一個 NioServerSocketChannle 對象;
- 調(diào)用
init(channel)
完成初始化工作; - 將NioServerSocketChannel進行了注冊。
initAndRegister()
篇幅較長,分析完畢了,我們再返回到doBind(final SocketAddress localAddress)
。在 doBind(final SocketAddress localAddress)
中如果 initAndRegister()
執(zhí)行完成,則 regFuture.isDone()
則為 true,執(zhí)行 doBind0()
。如果沒有執(zhí)行完成,則會注冊一個監(jiān)聽 ChannelFutureListener,當 initAndRegister()
完成后,會調(diào)用該監(jiān)聽的 operationComplete()
方法,最終目的還是執(zhí)行 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());
}
}
});
}
doBind0()
較為簡單,首先new 一個線程 task,然后將該任務提交到 NioEventLoop 中進行處理,我們先看 execute()
。
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
調(diào)用 inEventLoop()
判斷當前線程是否為該 NioEventLoop 所關(guān)聯(lián)的線程,如果是,則調(diào)用 addTask()
將任務 task 添加到隊列中,如果不是,則先啟動線程,在調(diào)用 addTask()
將任務 task 添加到隊列中。addTask()
如下:
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
if (!offerTask(task)) {
reject(task);
}
}
offerTask()
添加到隊列中:
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
task 添加到任務隊列 taskQueue成功后,執(zhí)行任務會調(diào)用如下方法:
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
channel 首先調(diào)用 bind()
完成 channel 與端口的綁定,如下:
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
tail 在 DefaultChannelPipeline 中定義:final AbstractChannelHandlerContext tail;
有 tail 就會有 head ,在 DefaultChannelPipeline 中維護這一個 AbstractChannelHandlerContext 節(jié)點的雙向鏈表,該鏈表是實現(xiàn) Pipeline 機制的關(guān)鍵,更多詳情會在 ChannelPipeline 中做詳細說明。bind()
最終會調(diào)用 DefaultChannelPipeline 的 bind()
方法。如下:
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (!validatePromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
首先對 localAddress 、 promise 進行校驗,符合規(guī)范則調(diào)用 findContextOutbound()
,該方法用于在 pipeline 中獲取 AbstractChannelHandlerContext 雙向鏈表中的一個節(jié)點,如下:
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
從該方法可以看出,所獲取的節(jié)點是從 tail 開始遍歷,獲取第一個節(jié)點屬性 outbound 為 true 的節(jié)點。其實該節(jié)點是 AbstractChannelHandlerContext 雙向鏈表的 head 節(jié)點。獲取該節(jié)點后,調(diào)用 invokeBind()
,如下:
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
handler()
返回的是 HeadContext 對象,然后調(diào)用其bind()
,如下:
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
unsafe 定義在 HeadContext 中,在構(gòu)造函數(shù)中初始化(unsafe = pipeline.channel().unsafe();
),調(diào)用 bind()
如下:
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.isRoot()) {
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
// 最核心方法
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
內(nèi)部調(diào)用 doBind()
,該方法為綁定中最核心的方法,位于 NioServerSocketChannel 中,如下:
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
javaChannel()
返回的是 NioServerSocketChannel 實例初始化時所產(chǎn)生的 Java NIO ServerSocketChannel 實例(ServerSocketChannelImple實例),然后調(diào)用其 bind()
,如下:
public ServerSocketChannel bind(SocketAddress var1, int var2) throws IOException {
Object var3 = this.lock;
synchronized(this.lock) {
if(!this.isOpen()) {
throw new ClosedChannelException();
} else if(this.isBound()) {
throw new AlreadyBoundException();
} else {
InetSocketAddress var4 = var1 == null?new InetSocketAddress(0):Net.checkAddress(var1);
SecurityManager var5 = System.getSecurityManager();
if(var5 != null) {
var5.checkListen(var4.getPort());
}
NetHooks.beforeTcpBind(this.fd, var4.getAddress(), var4.getPort());
Net.bind(this.fd, var4.getAddress(), var4.getPort());
Net.listen(this.fd, var2 < 1?50:var2);
Object var6 = this.stateLock;
synchronized(this.stateLock) {
this.localAddress = Net.localAddress(this.fd);
}
return this;
}
}
}
該方法屬于 Java NIO 層次的,該方法涉及到服務端端口的綁定,端口的監(jiān)聽,這些內(nèi)容在后續(xù)的 Channel 時做詳細介紹。
到這里就真正完成了服務端端口的綁定。
這篇博客比較長,大體上從源碼層次稍微解讀了 Netty 服務端的啟動過程,當中涉及到 Netty 的各個核心組件,只能籠統(tǒng)來描述服務端的啟動過程,具體的細節(jié)部分還需要后續(xù)做詳細分析,而且其中有多個點還是懵懵懂懂,相信在后面對 Netty 的分析過程會一一解答。
謝謝閱讀,祝好!!!
參考資料
- 《Netty權(quán)威指南(第二版)》
- 《Netty IN ACTION》
- Netty源碼分析:服務端啟動全過程(篇幅很長)
歡迎掃一掃我的公眾號關(guān)注 — 及時得到博客訂閱哦!