第 8 章 Netty 編解碼器和 handler 的調用機制

8.1 基本說明

  1. netty 的組件設計:Netty 的主要組件有 Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe 等

  2. ChannelHandler 充當了處理入站和出站數據的應用程序邏輯的容器。例如,實現 ChannelInboundHandler 接口(或ChannelInboundHandlerAdapter),你就可以接收入站事件和數據,這些數據會被業務邏輯處理。當要給客戶端發 送 響 應 時 , 也 可 以 從 ChannelInboundHandler 衝 刷 數 據 。 業 務 邏 輯 通 常 寫 在 一 個 或 者 多 個ChannelInboundHandler 中。ChannelOutboundHandler 原理一樣,只不過它是用來處理出站數據的

  3. ChannelPipeline 提供了 ChannelHandler 鏈的容器。以客戶端應用程序爲例,如果事件的運動方向是從客戶端到服務端的,那麼我們稱這些事件爲出站的,即客戶端發送給服務端的數據會通過 pipeline 中的一系列ChannelOutboundHandler,並被這些 Handler 處理,反之則稱爲入站的
    在這裏插入圖片描述

8.2 編碼解碼器

  1. 當 Netty 發送或者接受一個消息的時候,就將會發生一次數據轉換。入站消息會被解碼:從字節轉換爲另一種格式(比如 java 對象);如果是出站消息,它會被編碼成字節。

  2. Netty 提供一系列實用的編解碼器,他們都實現了 ChannelInboundHadnler 或者 ChannelOutboundHandler 接口。在這些類中,channelRead 方法已經被重寫了。以入站爲例,對於每個從入站 Channel 讀取的消息,這個方法會被調用。隨後,它將調用由解碼器所提供的 decode()方法進行解碼,並將已經解碼的字節轉發給 ChannelPipeline中的下一個 ChannelInboundHandler。

8.3 解碼器-ByteToMessageDecoder

  1. 關係繼承圖
    在這裏插入圖片描述

  2. 由於不可能知道遠程節點是否會一次性發送一個完整的信息,tcp 有可能出現粘包拆包的問題,這個類會對入站數據進行緩衝,直到它準備好被處理.

  3. 一個關於 ByteToMessageDecoder 實例分析
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述

8.4 Netty 的 handler 鏈的調用機制

實例要求:

  1. 使用自定義的編碼器和解碼器來說明 Netty 的 handler 調用機制
    客戶端發送 long -> 服務器
    服務端發送 long -> 客戶端
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
    在這裏插入圖片描述
  2. 案例演示
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

  1. public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

  2. ReplayingDecoder 擴展了 ByteToMessageDecoder 類,使用這個類, 我們不必調用 readableBytes() 方法。參數 S指定了用戶狀態管理的類型,其中 Void 代表不需要狀態管理

  3. 應用實例:使用 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());
        }**/
    }
}
  1. ReplayingDecoder 使用方便,但它也有一些侷限性:
  • 並 不 是 所 有 的 ByteBuf 操 作 都 被 支 持 , 如 果 調 用 了 一 個 不 被 支 持 的 方 法 , 將 會 拋 出 一 個
    UnsupportedOperationException。
  • ReplayingDecoder 在某些情況下可能稍慢於 ByteToMessageDecoder,例如網絡緩慢並且消息格式複雜時,
    消息會被拆成了多個碎片,速度變慢

8.6 其它編解碼器

8.6.1 其它解碼器

  1. LineBasedFrameDecoder:這個類在 Netty 內部也有使用,它使用行尾控制字符(\n 或者\r\n)作爲分隔符來解析數據。

  2. DelimiterBasedFrameDecoder:使用自定義的特殊字符作爲消息的分隔符。

  3. HttpObjectDecoder:一個 HTTP 數據的解碼器

  4. LengthFieldBasedFrameDecoder:通過指定長度來標識整包消息,這樣就可以自動的處理黏包和半包消息。

8.6.2 其它編碼器
在這裏插入圖片描述

8.7 Log4j 整合到 Netty

  1. 在 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>

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章