getbook netty实战_Netty 入门教程
前言
Netty是一個(gè)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,用于快速開發(fā)可維護(hù)的高性能協(xié)議服務(wù)器和客戶端。
Netty 是一個(gè)廣泛使用的 Java 網(wǎng)絡(luò)編程框架(Netty 在 2011 年獲得了Duke's Choice Award,見https://www.java.net/dukeschoice/2011)。它活躍和成長(zhǎng)于用戶社區(qū),像大型公司 Facebook 和 Instagram 以及流行 開源項(xiàng)目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強(qiáng)大的對(duì)于網(wǎng)絡(luò)抽象的核心代碼。
設(shè)計(jì)
針對(duì)多種傳輸類型的統(tǒng)一接口 - 阻塞和非阻塞
簡(jiǎn)單但更強(qiáng)大的線程模型
真正的無(wú)連接的數(shù)據(jù)報(bào)套接字支持
鏈接邏輯支持復(fù)用
易用性
完善的Javadoc
全面的代碼示例
性能
比核心的 Java API 更好的吞吐量,較低的延時(shí)
資源消耗更少,這個(gè)得益于共享池和重用
減少內(nèi)存拷貝
健壯性
消除由于慢、快、或重載連接產(chǎn)生的OutOfMemoryError
消除經(jīng)常發(fā)現(xiàn)在 NIO 在高速網(wǎng)絡(luò)中的應(yīng)用中的不公平讀/寫比
安全
完整的 SSL/ TLS 和 StartTLS 的支持
運(yùn)行在受限的環(huán)境例如 Applet 或 OSGI
社區(qū)
社區(qū)完善、更新/發(fā)布頻繁
背景1 - Reactor模型
wiki:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.
幾個(gè)關(guān)鍵點(diǎn):
事件驅(qū)動(dòng)(event handling)
可以處理一個(gè)或多個(gè)輸入源(one or more inputs)
通過Service Handler同步的將輸入事件(Event)采用多路復(fù)用分發(fā)給相應(yīng)的Request Handler(多個(gè))處理
背景2 - Java網(wǎng)絡(luò)編程(BIO)
經(jīng)典的BIO服務(wù)端:
一個(gè)主線程監(jiān)聽某個(gè)port,等待客戶端連接
當(dāng)接收到客戶端發(fā)起的連接時(shí),創(chuàng)建一個(gè)新的線程去處理客戶端請(qǐng)求
主線程重新回到監(jiān)聽port,等待下一個(gè)客戶端連接
缺點(diǎn):
每個(gè)新的客戶端Socket連接,都需要?jiǎng)?chuàng)建一個(gè)Thread處理,將會(huì)創(chuàng)建大量的線程
線程開銷較大,連接多時(shí),內(nèi)存耗費(fèi)大,CPU上下文切換開銷也大
背景3 - Java NIO
Java NIO 由以下幾個(gè)核心部分組成:
Channels
Buffers
Selectors
傳統(tǒng)IO基于字節(jié)流和字符流進(jìn)行操作,而NIO基于Channel和Buffer(緩沖區(qū))進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。
NIO和傳統(tǒng)IO(一下簡(jiǎn)稱IO)之間第一個(gè)最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。
Netty的重要組件
下面枚舉所有的Netty應(yīng)用程序的基本構(gòu)建模塊,包括客戶端和服務(wù)端。
BOOTSTRAP
Netty 應(yīng)用程序通過設(shè)置bootstrap(引導(dǎo))類的開始,該類提供了一個(gè)用于應(yīng)用程序網(wǎng)絡(luò)配置的容器。Netty有兩種類型的引導(dǎo): 客戶端(Bootstrap)和服務(wù)端(ServerBootstrap)
CHANNEL
底層網(wǎng)絡(luò)傳輸API必須提供給應(yīng)用I/O操作的接口,傳入(入站)或者傳出(出站)數(shù)據(jù)的載體,如讀,寫,連接,綁定等等。對(duì)于我們來(lái)說,這結(jié)構(gòu)幾乎總是會(huì)成為一個(gè)"socket"。
CHANNELHANDLER
ChannelHandler 支持很多協(xié)議,并且提供用于數(shù)據(jù)處理的容器。我們已經(jīng)知道ChannelHandler由特定事件觸發(fā)。ChannelHandler可專用于幾乎所有的動(dòng)作,包括一個(gè)對(duì)象轉(zhuǎn)為字節(jié),執(zhí)行過程中拋出的異常處理。
常用的一個(gè)接口是 ChannelInboundHandler,這個(gè)類型接收到入站事件(包括接收到的數(shù)據(jù))可以處理應(yīng)用程序邏輯。
當(dāng)你需要提供相應(yīng)時(shí),你也可以從ChannelInboundHandler沖刷數(shù)據(jù)。一句話,業(yè)務(wù)邏輯經(jīng)常存活于一個(gè)或者多個(gè)ChannelInboundHandler。
CHANNELPIPELINE
ChannelPipline提供了一個(gè)容器給 ChannelHandler鏈并提供了一個(gè)API用于管理沿著鏈入站和出站事件的流動(dòng)。每個(gè)Channel都有自己的ChannelPipeline,當(dāng)Channel創(chuàng)建時(shí)自動(dòng)創(chuàng)建的。
EVENTLOOP
EventLoop 用于處理 Channel 的 I/O 操作,控制流、多線程和并發(fā)。一個(gè)單一的 EventLoop通常會(huì)處理多個(gè) Channel 事件。一個(gè) EventLoopGroup 可以含有多于一個(gè)的 EventLoop 和 提供了一種迭代用于檢索清單中的下一個(gè)。
CHANNELFUTURE
Netty 所有的 I/O 操作都是異步。因?yàn)橐粋€(gè)操作可能無(wú)法立即返回,我們需要有一種方法在以后確定它的結(jié)果。
出于這個(gè)目的,Netty 提供了接口 ChannelFuture,它的 addListener 方法注冊(cè)了一個(gè) ChannelFutureListener ,當(dāng)操作完成時(shí),可以被異步通知(不管成功與否)。
以上組件的關(guān)系:
[站外圖片上傳中...(image-67dbed-1563459279939)]
幾點(diǎn)重要的約定:
一個(gè)EventLoopGroup包含一個(gè)或多個(gè)EventLoop
一個(gè)EventLoop在其生命周期內(nèi)只能和一個(gè)Thread綁定
EventLoop處理的I/O事件都由它綁定的Thread處理
一個(gè)Channel在其生命周期內(nèi),只能注冊(cè)于一個(gè)EventLoop
一個(gè)EventLoop可能被分配處理多個(gè)Channel。也就是EventLoop與Channel是1:n的關(guān)系
一個(gè)Channel上的所有ChannelHandler的事件由綁定的EventLoop中的I/O線程處理
不要阻塞Channel的I/O線程,可能會(huì)影響該EventLoop中其他Channel事件處理
第一個(gè) Netty 應(yīng)用: Echo client / server
本應(yīng)用的源碼請(qǐng)見 netty倉(cāng)庫(kù)中的example目錄。
接下來(lái),我們來(lái)構(gòu)建一個(gè)完整的Netty客戶端和服務(wù)器,更完整地了解Netty的API是如何實(shí)現(xiàn)客戶端和服務(wù)器的。
先來(lái)看看 Netty 應(yīng)用 - Echo client/server 總覽:
[站外圖片上傳中...(image-5996c5-1563459279939)]
echo應(yīng)用的客服端和服務(wù)器的交互很簡(jiǎn)單: 客戶端啟動(dòng)后,建立一個(gè)連接并發(fā)送一個(gè)或多個(gè)消息到服務(wù)端,服務(wù)端接受到的每個(gè)消息再返回給客戶端。
服務(wù)端代碼
一個(gè)信息處理器(handler): 這個(gè)實(shí)現(xiàn)是服務(wù)端的業(yè)務(wù)邏輯部分,當(dāng)連接創(chuàng)建后和接收信息后的處理類。
服務(wù)器: 主要通過ServerBootstrap設(shè)置服務(wù)器的監(jiān)聽端口等啟動(dòng)部分。
EchoServerHandler
通過繼承ChannelInboundHandlerAdapter,這個(gè)類提供了默認(rèn)的ChannelInboundHandler實(shí)現(xiàn),只需覆蓋以下的方法:
channelRead() - 每個(gè)消息入站都會(huì)調(diào)用
channelReadComplete() - 通知處理器最后的channelRead()是當(dāng)前批處理中的最后一條消息時(shí)調(diào)用
exceptionCaught() - 捕獲到異常時(shí)調(diào)用
@ChannelHandler.Sharable // 標(biāo)識(shí)這類的實(shí)例之間可以在 channel 里面共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in); // 將所接收的消息返回給發(fā)送者
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) // 沖刷所有待審消息到遠(yuǎn)程節(jié)點(diǎn)。關(guān)閉通道后,操作完成
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
EchoServer
創(chuàng)建ServerBootstrap實(shí)例來(lái)引導(dǎo)服務(wù)器,本服務(wù)端分配了一個(gè)NioEventLoopGroup實(shí)例來(lái)處理事件的處理,如接受新的連接和讀/寫數(shù)據(jù),然后綁定本地端口,分配EchoServerHandler實(shí)例給Channel,這樣服務(wù)器初始化完成,可以使用了。
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup(); // 創(chuàng)建 EventLoopGroup
try {
ServerBootstrap bootstrap = new ServerBootstrap(); // 創(chuàng)建 ServerBootstrap
bootstrap.group(group)
.channel(NioServerSocketChannel.class) // 指定使用 NIO 的傳輸 Channel
.localAddress(new InetSocketAddress(port)) // 設(shè)置 socket 地址使用所選的端口
.childHandler(new ChannelInitializer() { // 添加 EchoServerHandler 到 Channel 的 ChannelPipeline
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = bootstrap.bind().sync(); // 綁定的服務(wù)器;sync 等待服務(wù)器關(guān)閉
System.out.println(EchoServer.class.getName() + " started and listen on " + future.channel().localAddress());
future.channel().closeFuture().sync(); // 關(guān)閉 channel 和 塊,直到它被關(guān)閉
} finally {
group.shutdownGracefully().sync(); // 關(guān)閉 EventLoopGroup,釋放所有資源。
}
}
public static void main(String[] args) throws Exception {
int port = 4567;
if (args.length == 1) {
port = Integer.parseInt(args[0]);
}
new EchoServer(port).start(); // 設(shè)計(jì)端口、啟動(dòng)服務(wù)器
}
}
客戶端代碼
客戶端要做的是:
連接服務(wù)器
發(fā)送消息
等待和接受服務(wù)器返回的消息
關(guān)閉連接
EchoClientHandler
繼承SimpleChannelInboundHandler來(lái)處理所有的事情,只需覆蓋三個(gè)方法:
channelActive() - 服務(wù)器的連接被建立后調(diào)用
channelRead0() - 從服務(wù)器端接受到消息調(diào)用
exceptionCaught() - 捕獲異常處理調(diào)用
@ChannelHandler.Sharable // @Sharable 標(biāo)記這個(gè)類的實(shí)例可以在channel里共享
public class EchoClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); // 當(dāng)被通知該 channel 是活動(dòng)的時(shí)候就發(fā)送信息
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("Client received: " + byteBuf.toString(CharsetUtil.UTF_8)); // 記錄接收到的消息
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 記錄日志錯(cuò)誤并關(guān)閉 channel
cause.printStackTrace();
ctx.close();
}
}
EchoClient
通過Bootstrap引導(dǎo)創(chuàng)建客戶端,另外需要 host 、port 兩個(gè)參數(shù)連接服務(wù)器。
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap(); // 創(chuàng)建 Bootstrap
bootstrap.group(group) // 指定EventLoopGroup來(lái)處理客戶端事件。由于我們使用NIO傳輸,所以用到了 NioEventLoopGroup 的實(shí)現(xiàn)
.channel(NioSocketChannel.class) // 使用的channel類型是一個(gè)用于NIO傳輸
.remoteAddress(new InetSocketAddress(host, port)) // 設(shè)置服務(wù)器的InetSocketAddr
.handler(new ChannelInitializer() { // 當(dāng)建立一個(gè)連接和一個(gè)新的通道時(shí)。創(chuàng)建添加到EchoClientHandler實(shí)例到 channel pipeline
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect().sync(); // 連接到遠(yuǎn)程;等待連接完成
future.channel().closeFuture().sync(); // 阻塞到遠(yuǎn)程; 等待連接完成
} finally {
group.shutdownGracefully().sync(); // 關(guān)閉線程池和釋放所有資源
}
}
public static void main(String[] args) throws Exception {
final String host = "127.0.0.1";
final int port = 4567;
new EchoClient(host, port).start();
}
}
編譯和運(yùn)行 Echo
首先編譯、運(yùn)行服務(wù)端,會(huì)看到以下log:
me.icro.samples.echo.server.EchoServer started and listen on /0:0:0:0:0:0:0:0:4567
下一步是編譯、運(yùn)行客服端后,服務(wù)端會(huì)先接收到信息:
Server received: Netty rocks!
然后客戶端收到反饋:
Client received: Netty rocks!
總結(jié)
以上,構(gòu)建并運(yùn)行你的第一 個(gè)Netty 的客戶端和服務(wù)器。雖然這是一個(gè)簡(jiǎn)單的應(yīng)用程序,它可以擴(kuò)展到幾千個(gè)并發(fā)連接。
我們可以在Netty的Github倉(cāng)庫(kù)看到的更多 Netty 如何簡(jiǎn)化可擴(kuò)展和多線程的例子。
下一步的深入學(xué)習(xí),網(wǎng)上教程很多,大伙可以參考:
(完)
總結(jié)
以上是生活随笔為你收集整理的getbook netty实战_Netty 入门教程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 源码里没有configure_深入源码理
- 下一篇: python分支结构说课_Python_