【Netty】(6) ---原始碼ServerBootstrap
之前寫了兩篇與Bootstrap相關的文章,一篇是ServerBootstrap的父類,一篇是客戶端Bootstrap類,部落格地址:
【Netty】原始碼AbstractBootstrap
【Netty】原始碼 Bootstrap
所以接下來 有關ServerBootstrap 原始碼的分析,如果上面已經分析過了,就不再陳述。
一、概念
ServerBootstrap可以理解為伺服器啟動的工廠類,我們可以通過它來完成伺服器端的 Netty 初始化。
作用職責:EventLoop初始化
,channel的註冊過程
,關於pipeline的初始化
handler的新增過程
,服務端連線分析。下面也先看下原始碼
// 定義一對執行緒組
// 主執行緒組, 用於接受客戶端的連線,但是不做任何處理,跟老闆一樣,不做事
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 從執行緒組, 老闆執行緒組會把任務丟給他,讓手下執行緒組去做任務
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// netty伺服器的建立, 輔助工具類,用於伺服器通道的一系列配置
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup) //繫結兩個執行緒組
.channel(NioServerSocketChannel.class) //指定NIO的模式
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HelloServerInitializer()); // 子處理器,用於處理workerGroup
// 啟動server,並且設定8088為啟動的埠號,同時啟動方式為同步
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
// 監聽關閉的channel,設定位同步方式
channelFuture.channel().closeFuture().sync();
} finally {
//退出執行緒組
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
二、原始碼解析
1、group(bossGroup, workerGroup)
這裡跟客戶端明顯的一個區別就是,客戶端只傳入了一個NioEventLoopGroup,而服務端傳入了兩個。
/**
* 這裡呼叫的是 ServerBootstrap 類本身的 group 方法 發現傳入的兩個EventLoopGroup
* 一個賦值給父類(AbstractBootstrap),另一個賦值給 該物件本身屬性
*/
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
//呼叫父類的group方法
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}
而在伺服器端的初始化時, 我們設定一個是 bossGroup, 另一個是 workerGroup. 那麼這兩個 EventLoopGroup 到底是怎麼分工的?bossGroup 是用於服務端 的 accept 的, 即用於處理客戶端的連線請求.workerGroup 它們負責客戶端連線通道的 IO 操作
。
關於 bossGroup 與 workerGroup 的關係, 我們可以用如下圖來展示(盜圖):
首先, 伺服器端 bossGroup 不斷地監聽是否有客戶端的連線, 當發現有一個新的客戶端連線到來時, bossGroup 就會為此連線初始化各項資源,然後從 workerGroup 中選出一個 EventLoop 繫結到此客戶端連線中. 那麼接下來的伺服器與客戶端的互動過程就全部在此分配的 EventLoop 中了。至於 bossGroup 和 workerGroup 和 channel 如何聯絡到一起的,等下面再講bind(host)方法的時候在用原始碼展示,因為是通過bind(host)
開始將他們聯絡到一起的。
2、channel(NioServerSocketChannel.class)方法
這裡和上一篇說的channl是差不多的,具體原始碼可以看上一篇就可以了,只是這裡傳入的類是NioServerSocketChannel,而客戶端是NioSocketChannel,但他們都是通過類的反射
機制獲得類的物件的。同樣真正用到該物件的時候,也是在bind(host)方法裡。
有關NioServerSocketChannel物件和之前的NioSocketChannel物件本身是沒有講過的。
3、handler()和childHandler()
我們跟客戶端比較發現還是有明顯區別的, 和 EventLoopGroup 一樣, 伺服器端的 handler 也有兩個, 一個是通過 handler() 方法設定 handler 欄位, 另一個是通過
childHandler() 設定 childHandler 欄位。不過handler()方法並不是必須的,而childHandler()方法是必須呼叫的
。
看程式碼
/**handler(new LoggingHandler(LogLevel.INFO))
*
* 我們發現channel方法呼叫的是父類(AbstractBootstrap)的方法
* 所以這個 handler 欄位與 accept 過程有關, 即這個 handler 負責處理客戶端的連線請求
*/
public B handler(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
return self();
}
/** 再看childHandler(class)
*
*很明顯 這個childHandler 方法是屬於ServerBootstrap 本身的方法
* 所以推測: 這個childHandler 就是負責和客戶端的連線的 IO 互動
*/
public ServerBootstrap childHandler(ChannelHandler childHandler) {
if (childHandler == null) {
throw new NullPointerException("childHandler");
}
this.childHandler = childHandler;
return this;
}
有關handler和childHandler在哪個地方會被運用,等下將bind()方法的時候,我們在看他的原始碼。
4、bind(host)方法
bind(host)才是整個流程的關鍵,前面做得只是初始化了一些netty客戶端執行的物件(可以理解成只是建立了物件,並沒有使用它),但真正用到這些這些物件,
還是在bind(host)
方法裡。我們一步一步跟著原始碼走,裡面會省略一些不重要的程式碼
/**
* 1、呼叫父類(AbstractBootstrap)的方法
* <p>
* 作用: 根據埠號 建立一個InetSocketAddress物件,用於連線連線伺服器
*/
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
/**
* 2、繼續呼叫父類(AbstractBootstrap)的方法
* <p>
* 作用: 做一些校驗工作
*/
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
/**
* 3、繼續呼叫父類(AbstractBootstrap)的方法
* <p>
* 作用: 這個方法做了很多事情
*/
private ChannelFuture doBind(final SocketAddress localAddress) {
//3、1 具體看下面3、1的程式碼部分
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
}
}
/**
* 3、1 這步做了很多重要的事情
*/
final ChannelFuture initAndRegister() {
Channel channel = null;
//這裡終於呼叫newChannel方法了,這裡就是之前BootStrap講的ReflectiveChannelFactory物件的方法,這裡的
//channel 物件是NioServerSocketChannel。
channel = channelFactory.newChannel();
//這個方法也太重要了 和handle有關 下面3.1.1 講它
init(channel);
//這裡的group()獲取的就是bootstrap ,這裡面會呼叫next方法 來迴圈獲取下一個channel 具體的我就不點進去分析了
//這裡group().register(channel) 將 bossGroup 和 NioServerSocketChannel 關聯起來了.
ChannelFuture regFuture = config().group().register(channel);
return regFuture;
}
/**
* 3.1.1 首先可以看到into的方法在父類(AbstractBootstrap)已經提供,只是子類寫具體實現程式碼
*/
abstract void init(Channel channel) throws Exception;
/**
* 我們再來看ServerBootstrap實現了init方法,這裡面做了很多事
* 比如workerGroup相關,還有handel相關
*/
@Override
void init(Channel channel) throws Exception {
//通過channel獲得ChannelPipeline,說明每一個channel都會對應一個ChannelPipeline
ChannelPipeline p = channel.pipeline();
//這裡終於獲得workerGroup 物件
final EventLoopGroup currentChildGroup = childGroup;
//這裡獲得childHandler物件
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
//獲得handel方法傳入的物件
ChannelHandler handler = config.handler();
//這一步說明 .handler(new LoggingHandler(LogLevel.INFO))方法不是必須要的
//如果你沒有調handler方法也沒有關係 ,因為它會在這路做一層判斷
if (handler != null) {
pipeline.addLast(handler);
}
//到這裡執行緒就開始啟動運行了 發現已經講Channel,ChannelPipeline,workerGroup,childHandler等全部聯絡到了一起。
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
原始碼部落格推薦
有些原始碼自己也沒有去分析 比如:NioServerSocketChannel物件本身,ChannelPipeline物件。