Netty服務端
- Netty客戶端程序員啓動需要使用 Bootstrap 對象
- 和客戶端不同,服務端使用 ServerBootStrap 對象來啓動
Server代碼實現
- 通過和客戶端代碼實現對比,可以發現
- 服務端設置了兩個EventLoopGroup對象,分別用於處理客戶端連接請求和處理IO操作
- 與客戶端實現不同 服務端使用 ServerBootstrap 來實現程序啓動
- 使用 ServerBootstrap 的 bind 方法來啓動服務
public void start(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
...
}
});
ChannelFuture f = b.bind(this.port).sync();
log.info("服務已啓動,監聽端口" + this.port);
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
- 服務端使用ServerBootstrap而非Bootstarp
- ServerBootstrap同樣提供channel方法設置Channel Type
- 完成實例化操作的對象是 ReflectiveChannelFactory,通過反射調用到 NioServerSocketChannel 的構造方法
NioServerSocketChannel初始化和註冊
- 客戶端使用Channel Type爲NioSocketChannel
- 與客戶端不同的,服務端的入口是ServerBootstrap的bind方法,然後調用到父類的 doBind 方法完成 初始化和註冊
- 與客戶端不同的一點是,服務端傳入參數是 SelectionKey.OP_ACCEPT ,表示服務啓動後要監聽客戶端的連接請求
- 在服務端由於有兩個EventLoopGroup,所以Channel的註冊與客戶端略有不同
- 在initAndRegister方法中,首先完成了bossGroup和NioServerSocketChannel的關聯
- 接下來在init方法中完成了workerGroup和NioServerSocketChannel的關聯
- 當一個客戶端連接發送到服務端時,Java底層NIO的ServerSocketChannel會有已經初始化完成的 SelectionKey.OP_ACCEPT就緒
- 由NioServerSocketChannel的doReadMessages方法通過調用javaChannel().accept()方法獲取建立連接的SocketChannel對象
bossGroup和workerGroup
- 客戶端初始化流程的時候實例化了一個EventLoopGroup對象
- 而服務端實例化了兩個EventLoopGroup對象,其工作模型如下
- 服務端bossGroup不斷監聽是否有客戶端的連接,當拿到一個連接之後,bossGroup初始化各項資源
- 然後workerGroup中選出一個EventLoop與目標連接建立綁定
- 接下來與客戶端的交互過程在分配的 EventLoop 中完成
Selector事件輪詢
- 服務端啓動由ServerBootstrap的bind()方法開始,調用到AbstractBootstrap的doBind方法
- doBind0 方法,這裏調用了 與 channel關聯的 EventLoop 的execute方法 AbstractBootstrap -> doBind0
- 接下來調用到 SingleThreadEventExecutor 的 execute 方法
- 這裏主要做的事情就是創建線程,將線程添加到EventLoop的隊列中去
- SingleThreadEventExecutor.this.run方法,這裏的this實際上時EventLoop對象
- 這裏實現了具體的輪詢邏,體現了java NIO中的 select() 方法
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
本文總結
- Netty作爲服務端程序啓動與客戶端的區別
- 服務端使用NioServerSocketChannel進行初始化和註冊的基本過程
- boosGroup與workerGroup的區別及其工作模型
- 跟蹤源碼找到了相關代碼,體現了selector的輪詢機制