基本說明
Netty的組件設計:Netty主要組件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
ChannelHandler充當了處理入站和出站數據的應用程序邏輯的容器。例如,實現ChannelInboundHandler
接口(或ChannelInboundHandlerAdapter
),你就可以接收入站事件和數據,這些數據會被業務邏輯處理。業務邏輯通常寫在一個或者多個ChannelInboundHandler
中。ChannleOutboundHandler
原理一樣,只是它是用來處理出站數據的。
ChannelPipeline
提供了ChannleHandler
鏈的容器。以客戶端應用程序爲例,如果事件的運動方向是從客戶端到服務器端的,那麼我們稱這些事件是出站的,即客戶端發送給服務器端的數據會通過pipeline中的一系列ChannelOutboundHandler
,並被這些Handler處理,反之如果事件的運動方向是從服務器端到客戶端的則稱爲入站。
編解碼器
當Netty發送或者接收一個消息時,就會發生一次數據轉換。入站消息會被解碼,從二進制字節流轉爲對象等格式;如果是出站消息,則業務數據則會被編碼爲二進制字節流。
Netty提供了一系列實用的編解碼器,他們實現了ChannelInboundHandler
或者ChannelOutboundHandler
接口。這些編解碼器的實現類中,channelRead
方法普遍被重寫,以編解碼。
以入站爲例,channelRead
方法會被先調用,隨後,它將調用由解碼器所提供的decode()方法進行解碼,並將已經解碼的字節轉發給ChannelPipeline中的下一個ChannelInboundHandler。
解碼器接口ByteToMessageDecoder
、編碼器接口MessageToByteEncoder
,或者直接實現MessageToMessageCodec
包含了編解碼。當需要自定義編解碼器時,只需要實現接口即可。
自定義解碼器
例子:
public class ToIntegerDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannleHandlerContext ctx,ByteBuf in,List<Object> out) throws Exception{
if(in.readableBytes()>=4){
out.add(in.readInt());
}
}
}
說明:
每次入站從ByteBuf中讀取4個字節(因爲int是佔4個字節),將其解碼爲一個int,然後將它添加到下一個List中。當沒有更多元素可以被添加到List中時,list的內容會被髮送給下一個ChannleInboundHandler。這種情況下,判斷字節數是否>=4是必要的,否則會出現粘包拆包問題
下面以一個例子來說明Netty中handler鏈的調用順序
例子要求:
- 客戶端發送long給服務器端
- 服務器端發送long給客戶端
服務器端代碼
package com.wojiushiwo.codec;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class NettyCustomCodecServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encoder",new LongEncoder());
pipeline.addLast("decoder",new LongDecoder());
pipeline.addLast(new CustomCodecServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8091).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
//handler
package com.wojiushiwo.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class CustomCodecServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("調用了CustomCodecServerHandler#channelRead0");
System.out.println("from client:" + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("調用了CustomCodecServerHandler#channelReadComplete");
ctx.writeAndFlush(123456789L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客戶端代碼
package com.wojiushiwo.codec;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class NettyCustomCodecClient {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encoder", new LongEncoder());
pipeline.addLast("decoder", new LongDecoder());
pipeline.addLast(new CustomCodecClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8091).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
//handler
package com.wojiushiwo.codec;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by myk
* 2020/1/29 下午7:02
*/
public class CustomCodecClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客戶端CustomCodecClientHandler#channelActive");
ctx.writeAndFlush(1999999L);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("客戶端CustomCodecClientHandler#channelRead0");
System.out.println("from server:" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
自定義編解碼器代碼
//編碼器
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* Created by myk
* 2020/1/29 下午7:04
*/
public class LongEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("調用編碼器LongEncoder#encode");
out.writeLong(msg);
}
}
//解碼器
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* Created by myk
* 2020/1/29 下午7:03
*/
public class LongDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("調用LongDecoder#decode");
//long 8個字節
if (in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
服務器端輸出:
調用LongDecoder#decode
調用了CustomCodecServerHandler#channelRead0
from client:1999999
調用了CustomCodecServerHandler#channelReadComplete
調用編碼器LongEncoder#encode
客戶端輸出:
客戶端CustomCodecClientHandler#channelActive
調用編碼器LongEncoder#encode
調用LongDecoder#decode
客戶端CustomCodecClientHandler#channelRead0
from server:123456789
通過打印字符串到控制檯可以發現調用順序:
客戶端ChannelActive=>客戶端LongEncoder#encode=>服務器端LongDecoder#decode=>CustomCodecServerHandler#channelRead0=>CustomCodecServerHandler#channelReadComplete=>服務器端LongEncoder#encode=>客戶端LongDecoder#decode=>CustomCodecClientHandler#channelRead0
總結:
- 不論解碼器handler還是編碼器handler,接收的消息類型必須與待處理的消息類型一致,否則該handler不會被執行
- 在解碼器進行數據解碼時,需要判斷緩衝區的數據是否足夠,否則接收到的結果可能會與期望結果不一致。
解碼器-ReplayingDecoder
ReplayingDecoder
繼承了ByteToMessageDecoder
類,使用這個類,我們不需要顯式的去判斷緩衝區是否足夠,該類會自動去做這個事情。泛型S指定了用戶狀態管理的類型,其中Void表示不需要狀態管理。
示例:
使用ReplayingDecoder重構LongDecoder
package com.wojiushiwo.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
/**
* Created by myk
* 2020/1/29 下午7:47
*/
public class LongDecoder2 extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//不需要判斷數據是否足夠讀取,內部會進行處理判斷
out.add(in.readLong());
}
}
ReplayingDecoder優缺點:
優點:使用方便
缺點:
- 並不是所有的ByteBuf操作都被支持,如果調用了一個不被支持的方法,將會拋出
UnsupportOperationException
,比如ReplayingDecoderByteBuf#array()方法 - ReplayingDecoder在某些情況下會慢於ByteToMessageDecoder,如網絡緩慢且消息格式複雜時,消息會拆成了多個碎片,速度變慢。
其他編解碼器
- LineBasedFrameDecoder:這個類在Netty內部有使用,它使用行尾控制符(\n或者\r\n)作爲分隔符來解析數據
- DelimiterBasedFrameDecoder:使用自定義的特殊字符作爲消息的分割符
- HttpObjectDecoder:Http數據的解碼器
- LengthFieldBasedFrameDecoder:通過指定長度來標識整包信息,這樣就可以自動的處理粘包和半包問題
- ObjectEncoder:與對象有個的編碼器
- …