玩Netty,從「Hello World」開始!
玩Netty,從「Hello World」開始!
大家好,我是老三,之前裡,我們討論了Java的三種IO模型,提到了網路通訊框架Netty,它簡化和優化了NIO的使用,這期,我們正式開始走近Netty。
為什麼要用Netty?
首先當然是NIO的使用,本身比較複雜,而且還存在一些問題。
除此之外,如果在專案的開發中,要實現穩定的網路通信,就得考慮網路的閃斷、客戶端的重複接入、客戶端的安全認證、訊息的編解碼、半包讀寫…
所以,巧了,剛好有這麼一個成熟穩定、效能強大、開箱即用的網路框架擺在我們面前,相比較Java NIO,Netty更加出色:
- 易用性: Netty 在NIO 基礎上進行了更高層次的封裝,屏蔽了NIO 的複雜性,大大降低了開發難度;Netty 提供了很多開箱即用的工具,例如常用的行解碼器、長度域解碼器等,不需要自己再實作。
- 穩定性: Netty 更可靠穩定,修復並完善了JDK NIO 較多已知問題,例如臭名昭著的select 空轉導致CPU 消耗100%,TCP 斷線重連,keep-alive 檢測等問題。
- 可擴充性: Netty 的可擴充性做的非常好,例如支援可客製化的執行緒模型。
我們有什麼理由拒絕這麼一款優秀的網路通訊框架呢?程式碼怎麼寫不是寫嘍!
初識Netty
什麼是Netty?
Netty官方是這麼定義Netty的:
Netty 是一個非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協定伺服器和用戶端。
組成圖-來源官方
- Netty是一個開源的、單執行緒模型的Java 網路程式框架。
- Netty基於NIO ,廣泛應用於各種網路應用程式開發。
- Netty支援多種協議,包括但不限於HTTP、WebSocket、TCP、UDP 和SSL/TLS 協定等。
- Netty 是非阻塞的,事件驅動的框架。
- Netty具有高性能、可擴展和易於使用的優點。
Netty的現狀?
Netty 由JBoss 社群開發和維護的,它的社群相對比較活躍:
- https://github.com/netty/netty:Github已經收成31.2K星標
- https://netty.io/:官方網站,提供了比較完整的文檔
目前官方最新的版本是5.x,,但是很不幸,已經被社區放棄開發維護,屬於廢棄版本,最新的穩定版本是4.x 。
一般使用,建議4.x,Netty 4.x對3.x不相容,我們後續的學習也基於Netty 4.x版本。
誰在用Netty?
作為最受歡迎的網路通訊框架,大量的公司選擇它作為底層網路通訊框架,包括不限於:
使用Netty的公司
我們可能自己沒有直接用過Netty,但其實熟悉的很多開源中間件,都用到了Netty,例如:
- 服務治理:Apache Dubbo、gRPC。
- 大數據:Hbase、Spark、Flink、Storm。
- 搜尋引擎:Elasticsearch。
- 訊息佇列:RocketMQ、ActiveMQ。
使用Netty的優秀產品非常多,大家有興趣可以看看:https://netty.io/wiki/related-projects.html。
從"Hello World"開始
氣氛襯托到這,不寫Demo也過不去,還是從"Hello World"開始,我們領略一下Netty的風采。
- 建立一個Maven專案:這個就不用多說了吧
建立Maven項目
- 導入依賴:我們直接用4.x最新的版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.92.Final</version>
</dependency>
- 1.
- 2.
- 3.
- 4.
- 5.
- 寫程式碼:那我們就開始寫這個Demo的伺服器和客戶端相關程式碼
- NettyServer:基於Netty的客戶端
/**
* <p>Date: 2023/5/14 10:29</p>
* <p>Author: fighter3</p>
* <p>Description: Netty服务端Demo</p>
*/
public class NettyServer{
// 服务器监听的端口号
private int port;
public NettyServer(int port) {
this.port = port;
}
/**
* 启动Netty服务器
* @throws InterruptedException
*/
public void run() throws InterruptedException {
// 创建boss线程组和worker线程组
// bossGroup 用于监听客户端的连接请求,将连接请求发送给 workerGroup 进行处理
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// workerGroup 用于处理客户端连接的数据读写
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建 ServerBootstrap 对象,用于启动 Netty 服务器
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 绑定线程池事件组
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 通道初始化回调函数,在启动的时候可以自动调用
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加消息处理器
pipeline.addLast(new NettyServerHandler());
}
});
// 绑定端口,开始接收客户端请求
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
System.out.println("Netty服务器监听端口:"+port);
// 等待服务端监听端口关闭
channelFuture.channel().closeFuture().sync();
} finally {
//释放线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
// 创建服务器对象,监听端口号为 8888
NettyServer server = new NettyServer(8888);
System.out.println("============Netty服务器启动...=============");
// 启动服务器
server.run();
System.out.println("============Netty服务器停止...=============");
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- NettyServerHandler:伺服器的訊息處理器,用於處理各種事件
/**
* <p>Date: 2023/5/14 10:30</p>
* <p>Author: fighter3</p>
* <p>Description: Netty服务器消息处理器</p>
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 当客户端上线的时候会触发这个方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String message="你好,靓仔!";
ByteBuf hello = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
// 发送消息
ctx.writeAndFlush(hello);
}
/**
*当 Channel 中有来自客户端的数据时就会触发这个方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发来的消息:" + buf.toString(CharsetUtil.UTF_8)); // 接收消息并打印输出
}
/**
* 当有异常时触发这个方法
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- NettyClient:使用Netty的客戶端,透過ip和連接埠連接服務端
/**
* <p>Date: 2023/5/14 10:32</p>
* <p>Author: fighter3</p>
* <p>Description: Netty客户端Demo</p>
*/
public class NettyClient {
// 服务器 IP
private String host;
// 服务器监听的端口号
private int port;
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
/**
* 启动 Netty 客户端
*/
public void run() throws InterruptedException {
// 创建事件循环组
NioEventLoopGroup group = new NioEventLoopGroup();
try {
// 创建 Bootstrap 对象
Bootstrap bootstrap = new Bootstrap();
// 配置 Bootstrap 对象
// 设置线程组
bootstrap.group(group)
// 设置客户端通信的通道类型为NIO类型
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
// 通道初始化回调函数,在启动的时候可以自动调用
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 添加消息处理器
ch.pipeline().addLast(new NettyClientHandler());
}
});
// 连接服务器,异步等待连接成功
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
System.out.println("===========Netty客户端连接服务端=========");
// 等待客户端连接关闭
channelFuture.channel().closeFuture().sync();
} finally {
//释放资源
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
// 创建客户端对象,并连接到服务器
NettyClient client = new NettyClient("127.0.0.1", 8888);
// 启动客户端,开始发送消息
client.run();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- NettyClientHandler:Netty客戶端處理器,用於處各種事件
/**
* <p>Date: 2023/5/14 10:33</p>
* <p>Author: fighter3</p>
* <p>Description: Netty客户端处理器</p>
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当 Channel 准备就绪时就会触发这个方法
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String message="大佬,带带我!";
ByteBuf hello = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
// 发送消息
ctx.writeAndFlush(hello);
}
/**
* 当 Channel 中有来自服务器的数据时就会触发这个方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务端发来的消息:" + buf.toString(CharsetUtil.UTF_8)); // 接收消息并打印输出
}
/**
* 发生异常就会触发这个方法
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 運行一下:先啟動NettyServer,再啟動NettyClient,看看運行結果
============Netty服务器启动...=============
Netty服务器监听端口:8888
客户端发来的消息:大佬,带带我!
- 1.
- 2.
- 3.
===========Netty客户端连接服务端=========
服务端发来的消息:你好,靓仔!
- 1.
- 2.
好了,一個簡單的Netty入門Demo就寫完了,Netty是一個雙工通訊的網路框架,可以看到,服務端和客戶端,流程基本上一致,主要包括這麼多步驟:
- 建立事件循環群組和相關對象,用於監聽和處理網路事件;
- 設定Netty 伺服器或用戶端的啟動參數,包括執行緒群組、通道類型、TCP 參數等;
- 為伺服器或客戶端的ChannelPipeline 新增各種ChannelHandler,用於處理不同的網路事件;
- 綁定連接埠啟動伺服器或連接伺服器;
- 等待伺服器或客戶端連線關閉,釋放相關資源。
伺服器&客戶端初始化啟動流程
雖然這個Demo比較簡單,但其實已經用到了Netty裡幾個比較關鍵的組件:
- ByteBuf:Netty 的位元組容器,類似於Java 的ByteBuffer,但是提供了更強大、簡單且安全的API,用於在網路中傳遞二進位資料;
- EventLoopGroup:Netty 的事件循環群組,用於管理和調度連接到伺服器或從伺服器連接出去的所有Channel 上的事件循環;
- ServerBootstrap:Netty 的伺服器啟動類,用於啟動和設定一個TCP/IP 伺服器;
- Bootstrap:Netty 的客戶端啟動類,用於啟動和配置一個TCP/IP 用戶端;
- Channel:Netty 的核心概念,用來表示一個通訊通道,可以讀取和寫入資料;
- ChannelPipeline:Netty 的Channel 處理器,用於在傳入的資料上執行一組ChannelHandler;
- ChannelHandler:Netty 的核心元件,用於處理各種通訊事件,例如讀取資料、寫入資料、建立連線等;
Netty的重要元件
後續,我們也會和這些組件打更多的交道。
好了,那麼這期內容就到這了,這期裡我們初步了解了Netty,包括什麼是Netty、Netty現狀、Netty的應用,還寫了一個簡單的Demo。下一期,我們繼續深入了解Netty,敬請期待。
參考:
[1].https://netty.io/
[2].《Netty權威指南》
[3]. 《Netty核心原理剖析與RPC實務》