概述
在本文,我們將撰寫一個基于 Netty 實作的客戶端和服務端應用程式,相信通過學習該示例,一定能更全面的理解 Netty API

該圖展示的是多個客戶端同時連接到一臺服務器,客戶端建立一個連接后,會向服務器發送一個或多個訊息,反過來,服務器又會將每個訊息回送給客戶端
撰寫 Echo 服務器
所有 Netty 服務器都需要以下兩部分:
-
至少一個 CHannelHandler
該組件實作了服務器對從客戶端接收的資料的處理,即它的業務邏輯
-
引導
配置服務器的啟動代碼,將服務器系結到它要監聽連接請求的埠上
1. ChannelHandler 和業務邏輯
ChannelHandler 是一個介面族的父介面,它的實作負責接收并回應事件通知,即要包含資料的處理邏輯,我們的 Echo 服務器需要回應傳入的訊息,所以需要實作 ChannelHandler 介面,用來定義回應入站事件的方法,又因為只需要用到少量的方法,所以繼承 ChannelHandlerAdapter 類就足夠了,它提供了 ChannelHandler 的默認實作
我們感興趣的方法有:
-
channelRead()
對于每個傳入的訊息都要呼叫
-
channelReadComplete()
通知 ChannelHandler 最后一次對 channelRead() 的呼叫是當前批量讀取的最后一條訊息
-
exceptionCaught
在讀取操作期間,有例外拋出時會呼叫
Echo 服務器的 ChannelHandler 實作 EchoServerHandler 如下
@ChannelHandler.Sharable // 標識一個 ChannelHandler 可以被多個 Channel 安全的共享
public class EchoServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server receiver: " + in.toString(CharsetUtil.UTF_8));
// 將接收到的訊息寫給發送者
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
// 將剩余的訊息全部沖刷到遠程結點,并關閉 CHannel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
應用程式通過實作或者擴展 ChannelHandler 來掛鉤到事件的生命周期,并且提供自定義的應用程式邏輯,ChannelHandler 有助于保持業務邏輯與網路處理代碼的分離,簡化了開發程序
2. 引導服務器
撰寫完 EchoServerHandler 實作的核心業務邏輯之后,我們現在探討引導服務器的程序,具體涉及內容如下:
- 系結服務器將在其上監聽并接收傳入連接請求的介面
- 配置 Channel,將入站訊息交給 EchoServerHandler 實體
EchoServer 類完整代碼如下
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
return;
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
// 指定所使用的 NIO 傳輸 Channel
.channel(NioServerSocketChannel.class)
// 使用指定的埠設定套接字地址
.localAddress(new InetSocketAddress(port))
// 添加一個 EchoServerHandler 到子 Handler 的 ChannelPipeline
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(serverHandler);
}
});
// 異步地系結服務器,呼叫 sync() 方法阻塞等待直到系結完成
ChannelFuture f = b.bind().sync();
// 獲取 Channel 的 CloseFuture,并且阻塞當前執行緒直到它完成
f.channel().closeFuture().sync();
} finally {
// 關閉 EventLoopGroup 釋放所有資源
group.shutdownGracefully().sync();
}
}
}
到此為止,我們回顧一下服務器實作中的幾個重要步驟:
- EchoServerHandler 實作業務邏輯
- main() 方法引導服務器
引導服務器程序的重要步驟如下:
- 創建一個 ServerBootstrap 的實體以引導和系結服務器
- 創建并分配一個 NioEventLoopGroup 實體以進行事件的處理,如接受新連接以及讀寫資料
- 指定服務器系結的本地的 InetSocketAddress
- 使用一個 EchoServerHandler 實體初始化每一個新的 Channel
- 呼叫 ServerBootstrap.bind() 方法以系結服務器
撰寫 Echo 客戶端
Echo 客戶端的作用:
- 連接到服務器
- 發送一個或多個訊息
- 對于每個訊息,等待并接收從服務器回傳的回應
- 關閉連接
和服務器一樣,撰寫客戶端所涉及的主要代碼部分也是業務邏輯和引導
1. ChannelHandler 和客戶端邏輯
客戶端也要有一個用來處理資料的 ChannelHandler,這里選擇 SimpleChannelInboundHandler 類處理所有必需的任務,要求重寫下面的方法:
-
channelActive()
當與服務器的連接建立之后被呼叫
-
messageReceived()
當從服務器接收到一條訊息時被呼叫
-
exceptionCaught()
在處理程序中引發例外時被呼叫
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 當一個連接建立時被呼叫,發送一條訊息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) {
// 記錄已接收訊息的轉儲
System.out.println("Client received: " + msg.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 發生例外時,記錄錯誤并關閉 Channel
cause.printStackTrace();
ctx.close();
}
}
2. 引導客戶端
引導客戶端類似于服務器,不同的是,客戶端是使用主機和埠引數來連接遠程地址
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: " + EchoClient.class.getSimpleName() + "<host> <port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
// 創建 Bootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 連接到遠程節點,阻塞等待直到連接完成
ChannelFuture future = bootstrap.connect().sync();
// 阻塞,直到 Channel 關閉
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
到此為止,我們回顧一下客戶端實作中的幾個重要步驟:
- 創建一個 Bootstrap 實體
- 創建并分配一個 NioEventLoopGroup 實體以進行事件的處理,其中事件處理包括創建新的連接以及處理入站和出站資料
- 為服務器連接創建一個 InetSocketAddress 實體
- 當連接建立時,一個 EchoClientHandler 實體會被安裝到該 Channel 的 ChannelPipeline 中
- 呼叫 Bootstrap.connect() 方法連接遠程節點
運行客戶端和服務端
本文的專案使用 maven 構建,先啟動服務端并準備好接受連接,然后啟動客戶端,一旦客戶端建立連接,就會發送訊息,服務器接收訊息,控制臺會列印如下資訊:
Server receiver: Netty rocks!
同時將其回送給客戶端,客戶端的控制臺也會列印如下訊息,隨后退出:
Client received: Netty rocks!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/283007.html
標籤:Java
上一篇:mybatis快速入門
下一篇:spring-ioc的極簡實作
