[Netty學習筆記]十、編解碼器的使用

基本說明

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

總結:

  1. 不論解碼器handler還是編碼器handler,接收的消息類型必須與待處理的消息類型一致,否則該handler不會被執行
  2. 在解碼器進行數據解碼時,需要判斷緩衝區的數據是否足夠,否則接收到的結果可能會與期望結果不一致。
解碼器-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優缺點:

優點:使用方便

缺點:

  1. 並不是所有的ByteBuf操作都被支持,如果調用了一個不被支持的方法,將會拋出UnsupportOperationException,比如ReplayingDecoderByteBuf#array()方法
  2. ReplayingDecoder在某些情況下會慢於ByteToMessageDecoder,如網絡緩慢且消息格式複雜時,消息會拆成了多個碎片,速度變慢。
其他編解碼器
  1. LineBasedFrameDecoder:這個類在Netty內部有使用,它使用行尾控制符(\n或者\r\n)作爲分隔符來解析數據
  2. DelimiterBasedFrameDecoder:使用自定義的特殊字符作爲消息的分割符
  3. HttpObjectDecoder:Http數據的解碼器
  4. LengthFieldBasedFrameDecoder:通過指定長度來標識整包信息,這樣就可以自動的處理粘包和半包問題
  5. ObjectEncoder:與對象有個的編碼器
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章