一、編解碼器概述
1、編解碼器概述
當Netty發(fā)送或者接收一個消息的時候,就會發(fā)生一次數(shù)據(jù)轉(zhuǎn)換。入站消息會被解碼(從字節(jié)轉(zhuǎn)換為另一種格式,比如java對象);出站消息會被編碼成字節(jié)。
Netty 提供一系列實用的編解碼器,他們都實現(xiàn)了 ChannelInboundHadnler 或者 ChannelOutboundHandler 接口。在這些類中,channelRead 方法已經(jīng)被重寫了。以入站為例,對于每個從入站 Channel 讀取的消息,這個方法會被調(diào)用。隨后,它將調(diào)用由解碼器所提供的 decode()方法進行解碼,并將已經(jīng)解碼的字節(jié)轉(zhuǎn)發(fā)給 ChannelPipeline中的下一個 ChannelInboundHandler。
2、編碼器類關(guān)系圖
編碼器都繼承了MessageToByteEncoder抽象類,并且需要重寫其encode方法。
我們發(fā)現(xiàn),編碼器繼承了ChannelOutboundHandlerAdapter,表示出站操作才會執(zhí)行。
3、解碼器類關(guān)系圖
解碼器都繼承了ByteToMessageDecoder抽象類,并且需要重寫其decode方法。
我們發(fā)現(xiàn),解碼器繼承了ChannelInboundHandlerAdapter,表示入站操作才會執(zhí)行。
二、以編解碼器為例理解入站出站
1、Server端
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
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 ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();//一會下斷點
//入站的handler進行解碼 MyByteToLongDecoder
pipeline.addLast(new MyByteToLongDecoder());
//出站的handler進行編碼
pipeline.addLast(new MyLongToByteEncoder());
//自定義的handler 處理業(yè)務(wù)邏輯
pipeline.addLast(new MyServerHandler());
System.out.println("end");
}
}); //自定義初始化pipeline
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyServerHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("從客戶端" + ctx.channel().remoteAddress() + " 讀取到long " + msg);
//給客戶端發(fā)送一個long
ctx.writeAndFlush(98765L);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2、Client端
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;
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 ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//加入一個出站的handler 對數(shù)據(jù)進行一個編碼
pipeline.addLast(new MyLongToByteEncoder());
//這時一個入站的解碼器(入站handler )
pipeline.addLast(new MyByteToLongDecoder());
//加入一個自定義的handler , 處理業(yè)務(wù)
pipeline.addLast(new MyClientHandler());
}
}); //自定義一個初始化類
ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyClientHandler extends SimpleChannelInboundHandler<Long> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
System.out.println("服務(wù)器的ip=" + ctx.channel().remoteAddress());
System.out.println("收到服務(wù)器消息=" + msg);
}
//重寫channelActive 發(fā)送數(shù)據(jù)
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("MyClientHandler 發(fā)送數(shù)據(jù)");
ctx.writeAndFlush(123456L); //發(fā)送的是一個long
}
}
3、編解碼器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
//編碼方法
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
System.out.println("MyLongToByteEncoder encode 被調(diào)用");
System.out.println("msg=" + msg);
out.writeLong(msg);
}
}
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MyByteToLongDecoder extends ByteToMessageDecoder {
/**
*
* decode方法 會根據(jù)接收的數(shù)據(jù),被調(diào)用多次, 直到確定沒有新的元素被添加到list
* , 或者是ByteBuf 沒有更多的可讀字節(jié)為止
* 如果list out 不為空,就會將list的內(nèi)容傳遞給下一個 channelinboundhandler處理, 該處理器的方法也會被調(diào)用多次
*
* @param ctx 上下文對象
* @param in 入站的 ByteBuf
* @param out List 集合,將解碼后的數(shù)據(jù)傳給下一個handler
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder 被調(diào)用");
//因為 long 8個字節(jié), 需要判斷有8個字節(jié),才能讀取一個long
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
3、執(zhí)行查看結(jié)果
client端:
MyClientHandler 發(fā)送數(shù)據(jù)
MyLongToByteEncoder encode 被調(diào)用
msg=123456
MyByteToLongDecoder 被調(diào)用
服務(wù)器的ip=localhost/127.0.0.1:7000
收到服務(wù)器消息=98765
server端:
MyByteToLongDecoder 被調(diào)用
從客戶端/127.0.0.1:58478 讀取到long 123456
MyLongToByteEncoder encode 被調(diào)用
msg=98765
根據(jù)我們的執(zhí)行結(jié)果,我們可以簡單的得出一個結(jié)論,執(zhí)行流程大致是這樣的:
4、注意事項
編解碼器只能處理指定類型的數(shù)據(jù),如果數(shù)據(jù)類型不匹配,會跳過編解碼,但是數(shù)據(jù)不會丟失。
比如說客戶端ctx.writeAndFlush(Unpooled.copiedBuffer(“abcdabcdabcdabcd”,CharsetUtil.UTF_8)); 發(fā)送一個16位的字符串,Encoder的encode方法雖然會執(zhí)行,但是并不會編碼。因此我們編寫 Encoder 是要注意傳入的數(shù)據(jù)類型和處理的數(shù)據(jù)類型一致。
我們看一下MessageToByteEncoder的write方法:
// io.netty.handler.codec.MessageToByteEncoder#write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) {//判斷當前msg 是不是應(yīng)該處理的類型,如果是就處理,不是就跳過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);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
三、Netty其他內(nèi)置編解碼器
Netty內(nèi)含許多編解碼器,通常來說足夠應(yīng)對我們?nèi)粘9ぷ髁耍?/p>
1、ReplayingDecoder
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class MyByteToLongDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyByteToLongDecoder2 被調(diào)用");
//在 ReplayingDecoder 不需要判斷數(shù)據(jù)是否足夠讀取,內(nèi)部會進行處理判斷
out.add(in.readLong());
}
}
ReplayingDecoder 使用方便,但它也有一些局限性:
1、并不是所有的 ByteBuf操作都被支持,如果調(diào)用了一個不被支持的方法,將會拋出一個UnsupportedOperationException。
2、ReplayingDecoder 在某些情況下可能稍慢于 ByteToMessageDecoder,例如網(wǎng)絡(luò)緩慢并且消息格式復(fù)雜時,消息會被拆成了多個碎片,速度變慢。
2、其他編碼器
LineBasedFrameDecoder:這個類在 Netty 內(nèi)部也有使用,它使用行尾控制字符 (\n 或者\r\n)作為分隔符來解析數(shù)據(jù)。
DelimiterBasedFrameDecoder: 使用自定義的特殊字符作為消息的分隔符。
HttpObiectDecoder: 一個 HTTP 數(shù)據(jù)的解碼器。
LengthFieldBasedFrameDecoder: 通過指定長度來標識整包消息,這樣就可以自動的處理黏包和半包消息。
編碼器與解碼器都是成對出現(xiàn)的。
3、內(nèi)置編解碼器處理粘包拆包問題
Netty解決粘包和拆包問題的四種方案文章來源:http://www.zghlxwxcb.cn/news/detail-422002.html
四、自定義編解碼器實現(xiàn)粘包拆包問題
Netty解決粘包拆包問題,Netty使用自定義編解碼器解決粘包拆包問題文章來源地址http://www.zghlxwxcb.cn/news/detail-422002.html
到了這里,關(guān)于Netty編解碼器,Netty自定義編解碼器解決粘包拆包問題,Netty編解碼器的執(zhí)行過程詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!