8.1 基本說明
-
netty 的組件設計:Netty 的主要組件有 Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe 等
-
ChannelHandler 充當了處理入站和出站數據的應用程序邏輯的容器。例如,實現 ChannelInboundHandler 接口(或ChannelInboundHandlerAdapter),你就可以接收入站事件和數據,這些數據會被業務邏輯處理。當要給客戶端發 送 響 應 時 , 也 可 以 從 ChannelInboundHandler 衝 刷 數 據 。 業 務 邏 輯 通 常 寫 在 一 個 或 者 多 個ChannelInboundHandler 中。ChannelOutboundHandler 原理一樣,只不過它是用來處理出站數據的
-
ChannelPipeline 提供了 ChannelHandler 鏈的容器。以客戶端應用程序爲例,如果事件的運動方向是從客戶端到服務端的,那麼我們稱這些事件爲出站的,即客戶端發送給服務端的數據會通過 pipeline 中的一系列ChannelOutboundHandler,並被這些 Handler 處理,反之則稱爲入站的
8.2 編碼解碼器
-
當 Netty 發送或者接受一個消息的時候,就將會發生一次數據轉換。入站消息會被解碼:從字節轉換爲另一種格式(比如 java 對象);如果是出站消息,它會被編碼成字節。
-
Netty 提供一系列實用的編解碼器,他們都實現了 ChannelInboundHadnler 或者 ChannelOutboundHandler 接口。在這些類中,channelRead 方法已經被重寫了。以入站爲例,對於每個從入站 Channel 讀取的消息,這個方法會被調用。隨後,它將調用由解碼器所提供的 decode()方法進行解碼,並將已經解碼的字節轉發給 ChannelPipeline中的下一個 ChannelInboundHandler。
8.3 解碼器-ByteToMessageDecoder
-
關係繼承圖
-
由於不可能知道遠程節點是否會一次性發送一個完整的信息,tcp 有可能出現粘包拆包的問題,這個類會對入站數據進行緩衝,直到它準備好被處理.
-
一個關於 ByteToMessageDecoder 實例分析
8.4 Netty 的 handler 鏈的調用機制
實例要求:
- 使用自定義的編碼器和解碼器來說明 Netty 的 handler 調用機制
客戶端發送 long -> 服務器
服務端發送 long -> 客戶端
- 案例演示
Server端
public class MyServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MyServerInitializer()); //自定義一個初始化類
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {//出棧,入棧的Handler順序是不會衝突的
ChannelPipeline pipeline = ch.pipeline();//一會下斷點
//入站的handler進行解碼 MyByteToLongDecoder
pipeline.addLast(new MyByteToLongDecoder());
//出站的handler進行編碼
pipeline.addLast(new MyLongToByteEncoder());
//自定義的handler 處理業務邏輯
pipeline.addLast(new MyServerHandler());
System.out.println("xx");
}
/**
* 執行順序:
* MyByteToLongDecoder ----> MyServerHandler
*/
}
public class MyByteToLongDecoder extends ByteToMessageDecoder {
/**
*
* decode 會根據接收的數據,被調用多次, 直到確定沒有新的元素被添加到list
* , 或者是ByteBuf 沒有更多的可讀字節爲止
* 如果list out 不爲空,就會將list的內容傳遞給下一個 channelinboundhandler處理, 該處理器的方法也會被調用多次
*
* 如果Client發送16個字節的話,會被調用2次,同時下一個Handler也會執行2次,下面的結果
* xx
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
*
* @param ctx 上下文對象
* @param in 入站的 ByteBuf
* @param out List 集合,將解碼後的數據傳給下一個handler,InboundHandler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder 被調用");
//因爲 long 8個字節, 需要判斷有8個字節,才能讀取一個long
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
}
public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
//編碼方法
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("MyLongToByteEncoder encode 被調用");
System.out.println("msg=" + msg);
out.writeLong(msg);
}
}
public class MyServerHandler extends SimpleChannelInboundHandler<Long> {//MyByteToLongDecoder傳來的是Long
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("從客戶端" + ctx.channel().remoteAddress() + " 讀取到long " + msg);
//給客戶端發送一個long
ctx.writeAndFlush(98765L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Client端
public class MyClient {
public static void main(String[] args) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new MyClientInitializer()); //自定義一個初始化類
ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入一個出站的handler 對數據進行一個編碼 編碼成自己流
pipeline.addLast(new MyLongToByteEncoder());
//這時一個入站的解碼器(入站handler )
pipeline.addLast(new MyByteToLongDecoder());
//加入一個自定義的handler , 處理業務,肯定先將業務邏輯處理好的數據交給編碼器處理
pipeline.addLast(new MyClientHandler());//這裏是入棧的Handler
}
/**
* 執行順序:
* MyClientHandler -----> MyLongToByteEncoder
* MyClientHandler 發送數據
* MyLongToByteEncoder encode 被調用
* msg=123456
*/
}
public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
//編碼方法
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("MyLongToByteEncoder encode 被調用");
System.out.println("msg=" + msg);
out.writeLong(msg);
}
}
public class MyByteToLongDecoder extends ByteToMessageDecoder {
/**
*
* decode 會根據接收的數據,被調用多次, 直到確定沒有新的元素被添加到list
* , 或者是ByteBuf 沒有更多的可讀字節爲止
* 如果list out 不爲空,就會將list的內容傳遞給下一個 channelinboundhandler處理, 該處理器的方法也會被調用多次
*
* 如果Client發送16個字節的話,會被調用2次,同時下一個Handler也會執行2次,下面的結果
* xx
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
*
* @param ctx 上下文對象
* @param in 入站的 ByteBuf
* @param out List 集合,將解碼後的數據傳給下一個handler,InboundHandler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder 被調用");
//因爲 long 8個字節, 需要判斷有8個字節,才能讀取一個long
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
public class MyClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服務器的ip=" + ctx.channel().remoteAddress());
System.out.println("收到服務器消息=" + msg);
}
//重寫channelActive 發送數據
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("MyClientHandler 發送數據");
//這裏是Client端的 InboundHandler flush數據,從Client端的OutboundHandler發送出去到Socket到Server
ctx.writeAndFlush(123456L); //發送的是一個long
//如果發送下面的字符串“abcdabcdabcdabcd”,在Server會被調用2次,16個字節,一次讀取8個字節,會讀取2次,
// 但是這個時候 MyLongToByteEncoder 不會執行(原因下面有分析),Server會出現如下結果
/**
* xx
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
* MyByteToLongDecoder 被調用
* 從客戶端/127.0.0.1:52740 讀取到long 7017280452178371428
*
*/
//分析 MyLongToByteEncoder 不會執行的原因
//1. "abcdabcdabcdabcd" 是 16個字節
//2. 該處理器的前一個handler 是 MyLongToByteEncoder
//3. MyLongToByteEncoder 父類 MessageToByteEncoder
//4. 父類 MessageToByteEncoder
/*
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) { //判斷當前msg 是不是應該處理的類型,如果是就處理,不是就跳過encode
@SuppressWarnings("unchecked")
I cast = (I) msg;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
}
4. 因此我們編寫 Encoder 是要注意傳入的數據類型和處理的數據類型一致
*/
// ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd",CharsetUtil.UTF_8));
}
}
3) 結論
- 不論解碼器 handler 還是編碼器 handler 即接收的消息類型必須與待處理的消息類型一致,否則該 handler 不會被執行
- 在解碼器 進行數據解碼時,需要判斷 緩存區(ByteBuf)的數據是否足夠 ,否則接收到的結果會期望結果可能不一致
8.5 解碼器-ReplayingDecoder
-
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
-
ReplayingDecoder
擴展了ByteToMessageDecoder
類,使用這個類, 我們不必調用 readableBytes() 方法。參數 S指定了用戶狀態管理的類型,其中 Void 代表不需要狀態管理 -
應用實例:使用
ReplayingDecoder
編寫解碼器,對前面的案例進行簡化 [案例演示]
public class MyByteToLongDecoder2 extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder2 被調用");
//在 ReplayingDecoder 不需要判斷數據是否足夠讀取,內部會進行處理判斷
out.add(in.readLong());
/** 不用判斷了
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}**/
}
}
- ReplayingDecoder 使用方便,但它也有一些侷限性:
- 並 不 是 所 有 的 ByteBuf 操 作 都 被 支 持 , 如 果 調 用 了 一 個 不 被 支 持 的 方 法 , 將 會 拋 出 一 個
UnsupportedOperationException。 - ReplayingDecoder 在某些情況下可能稍慢於 ByteToMessageDecoder,例如網絡緩慢並且消息格式複雜時,
消息會被拆成了多個碎片,速度變慢
8.6 其它編解碼器
8.6.1 其它解碼器
-
LineBasedFrameDecoder
:這個類在 Netty 內部也有使用,它使用行尾控制字符(\n 或者\r\n)作爲分隔符來解析數據。 -
DelimiterBasedFrameDecoder
:使用自定義的特殊字符作爲消息的分隔符。 -
HttpObjectDecoder
:一個 HTTP 數據的解碼器 -
LengthFieldBasedFrameDecoder
:通過指定長度來標識整包消息,這樣就可以自動的處理黏包和半包消息。
8.6.2 其它編碼器
8.7 Log4j 整合到 Netty
- 在 Maven 中添加對 Log4j 的依賴 在 pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>