概述
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
Netty 是一個異步的、基于事件驅動的網絡應用架構,用于快速開發可維護、高性能的網絡伺服器和用戶端。
需要注意的是,Netty 中的異步操作是通過多路複用來實作的。在 Java NIO 中,可以使用一個線程同時處理多個通道的讀寫操作,這就是所謂的多路複用。Netty 正是基于 Java NIO 實作的,是以它也采用了多路複用技術來實作異步操作。
雖然 Netty 的異步操作并沒有實作真正意義上的異步 I/O,但是它的性能表現非常出色,能夠很好地滿足大部分應用的需求。同時,Netty 的程式設計模型比純 NIO 更加簡潔易用,可以幫助開發者快速建構高性能、可靠的網絡應用程式。
接下來,通過使用 Netty 建構服務端與用戶端,實作第一個 Netty demo。
服務端
1、首先,通過建立一個 ServerBootstrap 執行個體來啟動伺服器,它會組裝和配置 Netty 元件,并且啟動伺服器:
new ServerBootstrap()
2、使用 NioEventLoopGroup 類型的事件循環組作為 BossEventLoop 和 WorkerEventLoop,BossEventLoop 管理連接配接請求,WorkerEventLoop 管理連接配接的 I/O 資料處理:
group(new NioEventLoopGroup())
不熟悉的讀者可以看到部落客的上一篇博文 NIO-多線程優化,博文裡詳細介紹了 Boss 與 Worker 的用法;
3、選擇了 NioServerSocketChannel 來實作伺服器端監聽 Socket 的 Channel,表示該伺服器将使用 NIO 方式進行網絡通信:
channel(NioServerSocketChannel.class)
4、childHandler() 方法設定了一個初始化器,它将在每個新連接配接被接受時調用。該方法中的匿名内部類 ChannelInitializer 将為每個新連接配接添加一個新的管道 pipeline,并将 initChannel() 方法回調給這個新的管道:
childHandler(
// channel 初始化,負責添加别的 handler
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nsc) throws Exception {
...
}
}
)
5、向 initChannel() 方法中添加兩個 handler,分别是 StringDecoder 和 ChannelInboundHandlerAdapter,其中 StringDecoder 是 Netty 提供的一個具體的消息解碼器,将位元組流轉換成字元串;ChannelInboundHandlerAdapter 則是自定義的消息處理器,當有消息到達時,将其列印出來:
@Override
protected void initChannel(NioSocketChannel nsc) throws Exception {
nsc.pipeline().addLast(new StringDecoder());
nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
6、調用 bind() 方法,監聽特定的端口号 7999,以開始接受來自用戶端的連接配接請求:
bind(7999);
7、完整代碼:
public class HelloServer {
public static void main(String[] args) {
// 1. 啟動器,負責組裝 netty 元件并啟動伺服器
new ServerBootstrap()
// 2. BossEventLoop, WorkerEventLoop(selector, thread)
.group(new NioEventLoopGroup())
// 3. 選擇伺服器的 ServerSocketChannel 實作
.channel(NioServerSocketChannel.class)
// 4. 配置 worker(child) 能執行的操作 handler
.childHandler(
// 5. channel 初始化,負責添加别的 handler
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nsc) throws Exception {
// 6. 添加具體的 handler
nsc.pipeline().addLast(new StringDecoder());
nsc.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
});
}
}
)
// 7. 綁定監聽端口
.bind(7999);
}
}
用戶端
1、建立 Bootstrap 執行個體,它是 Netty 庫中用于建立用戶端的啟動類:
new ServerBootstrap()
2、添加一個 NioEventLoopGroup 執行個體作為 EventLoop,用于處理 I/O 操作:
group(new NioEventLoopGroup())
3、設定 channel 類型為 NioSocketChannel,表示使用 NIO 進行網絡通信:
channel(NioServerSocketChannel.class)
4、添加一個 ChannelInitializer 執行個體,在連接配接建立後對 channel 進行初始化。這裡添加了一個 StringEncoder,用于将字元串編碼成位元組流以進行傳輸:
handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nsc) throws Exception {
nsc.pipeline().addLast(new StringEncoder());
}
})
5、調用 connect 方法,與伺服器建立連接配接,并傳回一個 ChannelFuture 執行個體,通過調用 sync 方法等待連接配接成功:
.connect(new InetSocketAddress(7999))
.sync()
6、擷取連接配接成功後的 channel 執行個體,通過 writeAndFlush 方法向伺服器發送字元串消息:
.channel()
.writeAndFlush("Hello World! --sidiot.");
7、完整代碼:
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
// 1. 啟動用戶端
new Bootstrap()
// 2. 添加 EventLoop
.group(new NioEventLoopGroup())
// 3. 選擇 channel 實作
.channel(NioSocketChannel.class)
// 4. 添加 handler
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nsc) throws Exception {
nsc.pipeline().addLast(new StringEncoder());
}
})
// 5. 連接配接到伺服器
.connect(new InetSocketAddress(7999))
.sync()
.channel()
// 6. 向伺服器發送資料
.writeAndFlush("Hello World! --sidiot.");
}
}
運作結果:
Hello World! --sidiot.
流程分析
伺服器先行啟動,步驟1到步驟5按代碼順序進行執行,但是 initChannel 需要在建立連接配接後才會被執行,是以步驟6是綁定監聽端口 bind(7999);
再啟動用戶端,步驟7到步驟11按代碼順序進行執行,步驟12在連接配接建立後對 channel 進行初始化,步驟13中 sync() 方法等待連接配接成功。
然後進行步驟14,用戶端向服務端發送資料,在這個過程中,資料會先經過步驟15進行加密,再發送至服務端,由步驟16中相應的 eventLoop 進行處理,步驟17将接收到的資料進行解密,最後是步驟18,執行 read 方法,列印資料。
在這些步驟中,用到了 channel,handler 和 eventLoop 等元件,接下來稍作解釋:
- channel:資料的傳輸通道;
- handler:資料的處理工序: handler 分為 Inbound 和 Outbound 兩類:Inbound 表示入站,Outbound 表示出站; pipeline 代表了 Netty 中的一個處理鍊,負責釋出事件(讀、讀取完成等)傳播給每個 handler, handler 對自己感興趣的事件進行處理(重寫了相應事件處理方法),每個 handler 都會按順序依次處理傳入和傳出的資料流,直到最後一個完成其工作并将響應發送回用戶端。
- eventLoop:處理資料的勞工: eventLoop 可以管理多個 channel 的 I/O 操作,并且一旦 eventLoop 負責了某個 channel,就會将其與這個 channel進行綁定,以後該 channel 中的 I/O 操作都由該 eventLoop 負責; eventLoop 既可以執行 I/O 操作,也可以進行任務處理,每個 eventLoop 有自己的任務隊列,隊列裡可以堆放多個 channel 的待處理任務,任務分為普通任務、定時任務; eventLoop 按照 pipeline 順序,依次按照 handler 的規劃(代碼)處理資料,可以為每個 handler 指定不同的 eventLoop;
後記
以上就是 從0到1(六):入門-Hello World 的所有内容了,希望本篇博文對大家有所幫助!
連結:https://juejin.cn/post/7235539402867949605