1 為什么要用 Netty
1 雖然 JAVA NIO 框架提供了 多路復用 IO 的支持,但是并沒有提供上層“資訊格式”的良好封裝,例如前兩者并沒有提供針對 Protocol Buffer、JSON 這些資訊格式的封裝,但是Netty 框架提供了這些資料格式封裝(基于責任鏈模式的編碼和解碼功能),
2 NIO 的類別庫和 API 相當復雜,使用它來開發,需要非常熟練地掌握 Selector、ByteBuffer、ServerSocketChannel、SocketChannel 等,需要很多額外的編程技能來輔助使用 NIO,例如,因為 NIO 涉及了 Reactor 執行緒模型,所以必須必須對多執行緒和網路編程非常熟悉才能寫出高質量的 NIO 程式,
3 要撰寫一個可靠的、易維護的、高性能的 NIO 服務器應用,除了框架本身要兼容實作各類作業系統的實作外,更重要的是它應該還要處理很多上層特有服務,例如:客戶端的權限、還有上面提到的資訊格式封裝、簡單的資料讀取,斷連重連,半包讀寫,心跳等等,這些 Netty 框架都提供了回應的支持
4 Netty 的性能很高,按照 Facebook 公司開發小組的測驗表明,Netty 最高能達到接近百萬的吞吐:

2 Netty核心組件
2.1 Channel
基本的 I/O 操作(bind()、connect()、read()和 write())依賴于底層網路傳輸所提供的原語,在基于 Java 的網路編程中,其基本的構造是類 Socket,Netty 的 Channel 介面所提供的 API,被用于所有的 I/O 操作,大大地降低了直接使用 Socket 類的復雜性,此外,Channel也是擁有許多預定義的、專門化實作的廣泛類層次結構的根,
生命周期:
ChannelUnregistered :Channel 已經被創建,但還未注冊到 EventLoop,
ChannelRegistered :Channel 已經被注冊到了 EventLoop,
ChannelActive :Channel 處于活動狀態(已經連接到它的遠程節點),它現在可以接收和發送資料了,
ChannelInactive :Channel 沒有連接到遠程節點,

2.2 EventLoop、EventLoopGroup
Netty 基于事件驅動模型,使用不同的事件來通知我們狀態的改變或者操作狀態的改變,它定義了在整個連接的生命周期里當有事件發生的時候處理的核心抽象,
Channel 為Netty 網路操作抽象類,EventLoop 主要是為Channel 處理 I/O 操作,兩者配合參與 I/O 操作,
下圖是Channel、EventLoop、Thread、EventLoopGroup之間的關系:

(1) 一個 EventLoopGroup 包含一個或多個 EventLoop,
(2) 一個 EventLoop 在它的生命周期內只能與一個Thread系結,
(3) 所有有 EnventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理,
(4) 一個 Channel 在它的生命周期內只能注冊與一個 EventLoop,
(5) 一個 EventLoop 可被分配至一個或多個 Channel ,
2.3 ChannelHandler
從應用程式開發人員的角度來看,Netty 的主要組件是 ChannelHandler,它充當了所有處理入站和出站資料的應用程式邏輯的容器,ChannelHandler 的方法是由網路事件觸發的,事實上,ChannelHandler 可專門用于幾乎任何型別的動作,例如將資料從一種格式轉換為另外一種格式,例如各種編解碼,或者處理轉換程序中所拋出的例外,
舉例來說,ChannelInboundHandler 是一個你將會經常實作的子介面,這種型別的ChannelHandler 接收入站事件和資料,這些資料隨后將會被你的應用程式的業務邏輯所處理,當你要給連接的客戶端發送回應時,也可以從 ChannelInboundHandler 直接沖刷資料然后輸出到對端,應用程式的業務邏輯通常實作在一個或者多個 ChannelInboundHandler 中,
這種型別的 ChannelHandler 接收入站事件和資料,這些資料隨后將會被應用程式的業務邏輯所處理,Netty 定義了下面兩個重要的 ChannelHandler 子介面:
ChannelInboundHandler: 處理入站資料以及各種狀態變化;
ChannelOutboundHandler: 處理出站資料并且允許攔截所有的操作,
ChannelHandler 的配接器 :
有一些配接器類可以將撰寫自定義的 ChannelHandler 所需要的作業降到最低限度,因為它們提供了定義在對應介面中的所有方法的默認實作,因為你有時會忽略那些不感興趣的事件,所以 Netty 提供了抽象基類 ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter,
你可以使用 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 類作為自己的 ChannelHandler 的起始點,這兩個配接器分別提供了 ChannelInboundHandler 和ChannelOutboundHandler 的基本實作,通過擴展抽象類 ChannelHandlerAdapter,它們獲得了它們共同的超介面 ChannelHandler 的方法,

資源管理和 SimpleChannelInboundHandler :
Netty 在處理網路資料時,同樣也需要 Buffer,在 Read 網路資料時由 Netty 創建 Buffer,Write 網路資料時 Buffer 往往是由業務方創建的,不管是讀和寫,Buffer 用完后都必須進行釋放,否則可能會造成記憶體泄露,
在 Write 網路資料時,可以確保資料被寫往網路了,Netty 會自動進行 Buffer 的釋放,但是如果 Write 網路資料時,我們有 outBoundHandler 處理了 write()操作并丟棄了資料,沒有繼續往下寫,要由我們負責釋放這個 Buffer,就必須呼叫 ReferenceCountUtil.release 方法,否則就可能會造成記憶體泄露,
在 Read 網路資料時,如果我們可以確保每個 InboundHandler 都把資料往后傳遞了,也就是呼叫了相關的 fireChannelRead 方法,Netty 也會幫我們釋放,同樣的,如果我們有InboundHandler 處理了資料,又不繼續往后傳遞,又不呼叫負責釋放的ReferenceCountUtil.release 方法,就可能會造成記憶體泄露,
但是由于消費入站資料是一項常規任務,所以 Netty 提供了一個特殊的被稱為 SimpleChannelInboundHandler 的 ChannelInboundHandler 實作,這個實作會在資料被 channelRead0()方法消費之后自動釋放資料,

同時系統為我們提供的各種預定義 Handler 實作,都實作了資料的正確處理,所以我們自行在撰寫業務 Handler 時,也需要注意這一點: 要么繼續傳遞,要么自行釋放,
2.4 ChannelPipeline
當 Channel 被創建時,它將會被自動地分配一個新的 ChannelPipeline,這項關聯是永久性的;Channel 既不能附加另外一個 ChannelPipeline,也不能分離其當前的,在 Netty 組件的生命周期中,這是一項固定的操作,不需要開發人員的任何干預,
使得事件流經 ChannelPipeline 是 ChannelHandler 的作業,它們是在應用程式的初始化或者引導階段被安裝的,這些物件接收事件、執行它們所實作的處理邏輯,并將資料傳遞給鏈中的下一個 ChannelHandler,它們的執行順序是由它們被添加的順序所決定的,
ChannelPipeline 中 ChannelHandler :
入站和出站 ChannelHandler 可以被安裝到同一個 ChannelPipeline 中,如果一個訊息或者任何其他的入站事件被讀取,那么它會從 ChannelPipeline 的頭部開始流動,最終,資料將會到達 ChannelPipeline 的尾端,屆時,所有處理就都結束了,
資料的出站運動(即正在被寫的資料)在概念上也是一樣的,在這種情況下,資料將從ChannelOutboundHandler 鏈的尾端開始流動,直到它到達鏈的頭部為止,在這之后,出站資料將會到達網路傳輸層,這里顯示為 Socket,通常情況下,這將觸發一個寫操作,
如果將兩個類別的ChannelHandler都混合添加到同一個ChannelPipeline 中會發生什么,雖然 ChannelInboundHandle 和 ChannelOutboundHandle 都擴展自 ChannelHandler,但是Netty 能區分 ChannelInboundHandler 實作和 ChannelOutboundHandler 實作,并確保資料只會在具有相同定向型別的兩個 ChannelHandler 之間傳遞,

2.5 ChannelHandlerContext
通過使用作為引數傳遞到每個方法的 ChannelHandlerContext,事件可以被傳遞給當前ChannelHandler 鏈中的下一個 ChannelHandler,雖然這個物件可以被用于獲取底層的Channel,但是它主要還是被用于寫出站資料,
ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之間的關聯,每當有ChannelHandler 添加到 ChannelPipeline 中時,都會創建 ChannelHandlerContext,ChannelHandlerContext 的主要功能是管理它所關聯的 ChannelHandler 和在同一個ChannelPipeline 中的其他 ChannelHandler 之間的互動,

ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel 和Channel-Pipeline 本身上, 但是有一點重要的不同, ,如果呼叫Channel 或者ChannelPipeline 上的這些方法,它們將沿著整個 ChannelPipeline 進行傳播,而呼叫位于 ChannelHandlerContext上的相同方法,則將從當前所關聯的 ChannelHandler 開始,并且只會傳播給位于該ChannelPipeline 中的下一個(入站下一個,出站上一個)能夠處理該事件的 ChannelHandler,

3 第一個 Netty 程式

3.1 pom
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.28.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<!--工具-->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<exclusions>
<exclusion>
<artifactId>mail</artifactId>
<groupId>javax.mail</groupId>
</exclusion>
<exclusion>
<artifactId>jms</artifactId>
<groupId>javax.jms</groupId>
</exclusion>
<exclusion>
<artifactId>jmxtools</artifactId>
<groupId>com.sun.jdmk</groupId>
</exclusion>
<exclusion>
<artifactId>jmxri</artifactId>
<groupId>com.sun.jmx</groupId>
</exclusion>
</exclusions>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3.2 EchoServer
package com.rosh.echo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @Description:
* @Author: Rosh
* @Date: 2021/4/1 15:15
*/
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
//服務端啟動類
ServerBootstrap serverBootstrap = new ServerBootstrap();
//執行緒組
EventLoopGroup group = new NioEventLoopGroup();
try {
//handler
final EchoServerHandler echoServerHandler = new EchoServerHandler();
/**
* (1) 服務端系結執行緒組
* (2) 設定channel通道型別為NIO
* (3) 指定監聽埠
* (4) 添加EchoServerHandler
*/
serverBootstrap.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(port)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(echoServerHandler);
}
});
//異步系結服務器,sync阻塞到系結完成
ChannelFuture channelFuture = serverBootstrap.bind().sync();
System.out.println("服務器已經啟動啦......");
//阻塞當前執行緒,直到服務器的channel關閉
channelFuture.channel().closeFuture().sync();
} finally {
/**
* 優雅退出
*/
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer(8888).start();
}
}
3.3 EchoServerHandler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server accept:" + in.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush(in);
ctx.close();
}
}
3.4 EchoClient
package com.rosh.echo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* @Description:
* @Author: Rosh
* @Date: 2021/4/1 15:15
*/
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host,int port) {
this.port = port;
this.host = host;
}
public void start() throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
final EchoClientHandler echoClientHandler = new EchoClientHandler();
/**
* 設定執行緒組、設定通道型別、系結遠端地址、設定handler
*/
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(echoClientHandler);
}
});
//異步鏈接服務器
ChannelFuture channelFuture = bootstrap.connect().sync();
//阻塞
channelFuture.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoClient("127.0.0.1",8888).start();
}
}
3.5 EchoClientHandler
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 讀取到網路資料后進行處理
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("client Accept: " + msg.toString(CharsetUtil.UTF_8));
}
/**
* channel 連接成功后做業務處理
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello Netty", CharsetUtil.UTF_8));
}
}
3.6 啟動服務端、客戶端


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/271973.html
標籤:java
