Netty——EmbeddedChannel類

EmbeddedChannel類

Netty 提供了它所謂的 Embedded 傳輸,用於測試 ChannelHandler。這個傳輸是一種特殊的 Channel 實現— EmbeddedChannel— 的功能,這個實現提供了通過 ChannelPipeline 傳播事件的簡便方法。

這個想法是直截了當的:將入站數據或者出站數據寫入到 EmbeddedChannel 中,然後檢 查是否有任何東西到達了 ChannelPipeline 的尾端。以這種方式,你便可以確定消息是否已 經被編碼或者被解碼過了,以及是否觸發了任何的 ChannelHandler 動作。

在這裏插入圖片描述

入站數據由 ChannelInboundHandler 處理,代表從遠程節點讀取的數據。出站數據由 ChannelOutboundHandler 處理,代表將要寫到遠程節點的數據。根據你要測試的 ChannelHandler,你將使用*Inbound()或者*Outbound()方法對,或者兼而有之。

下圖展示了使用 EmbeddedChannel 的方法,數據是如何流經 ChannelPipeline 的。 你可以使用 writeOutbound()方法將消息寫到 Channel 中,並通過 ChannelPipeline 沿 着出站的方向傳遞。隨後,你可以使用 readOutbound()方法來讀取已被處理過的消息,以確 定結果是否和預期一樣。類似地,對於入站數據,你需要使用 writeInbound()和 readInbound() 方法。

在這裏插入圖片描述
在每種情況下,消息都將會傳遞過 ChannelPipeline,並且被相關的 ChannelInbound- Handler 或者 ChannelOutboundHandler 處理。如果消息沒有被消費,那麼你可以使用 readInbound()或者 readOutbound()方法來在處理過了這些消息之後,酌情把它們從 Channel 中讀出來。

使用EmbeddedChannel測試ChannelHandler

測試入站消息:
下圖展示了一個簡單的ByteToMessageDecoder實現。給定足夠的數據,這個實現將 產生固定大小的幀。如果沒有足夠的數據可供讀取,它將等待下一個數據塊的到來,並將再次檢 查是否能夠產生一個新的幀。
在這裏插入圖片描述
這個特定的解碼器將產生固定爲 3 字節大小的幀。
因此,它可能會需要多個事件來提供足夠的字節數以產生一個幀。最終,每個幀都會被傳遞給 ChannelPipeline 中的下一個 ChannelHandler。該解碼器的實現,如下所示:

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
    private final int frameLength;

    public FixedLengthFrameDecoder(int frameLength) {
        if (frameLength <= 0) {
            throw new IllegalArgumentException("frameLength must be a positive integer:" + frameLength);
        }
        this.frameLength = frameLength;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //檢查是否有足夠的字節可以被讀取,以生成下一個幀
        while (in.readableBytes() >= frameLength) {
            //從ByteBuf中讀取一個新幀
            ByteBuf buf = in.readBytes(frameLength);
            //將該幀添加到已被解碼的消息列表中
            out.add(buf);
        }
    }
}

測試FixedLengthFrameDecoder:

public class FixedLengthFrameDecoderTest {
    @Test
    public void testFrameDecoded() {
        //創建一個ByteBuf,並存儲9字節
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);
        }
        ByteBuf input = buf.duplicate();
        EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
        //將數據寫入EmbeddedChannel
        System.out.println(channel.writeInbound(input.retain()));//true
        //標記Channel爲已完成狀態
        System.out.println(channel.finish());//true

        //讀取所生成的消息,並且驗證是否有3幀,其中每幀都爲3字節
        ByteBuf read = channel.readInbound();
        System.out.println(buf.readSlice(3).equals(read));//true

        read = channel.readInbound();
        System.out.println(buf.readSlice(3).equals(read));//true
        read.release();

        read = channel.readInbound();
        System.out.println(buf.readSlice(3).equals(read));//true
        read.release();

        System.out.println(channel.readInbound() == null);//true
        buf.release();
    }

    @Test
    public void testFramesDescode2() {
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);
        }
        ByteBuf input = buf.duplicate();
        EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
        //返回false,因爲沒有一個完整的可供讀取的幀
        System.out.println(channel.writeInbound(input.readBytes(2)));//false
        System.out.println(channel.writeInbound(input.readBytes(7)));//true

        System.out.println(channel.finish());//true
        ByteBuf read = channel.readInbound();
        System.out.println(buf.readSlice(3) == read);//false
        read.release();

        read = channel.readInbound();
        System.out.println(buf.readSlice(3) == read);//false
        read.release();

        read = channel.readInbound();
        System.out.println(buf.readSlice(3) == read);//false
        read.release();

        System.out.println(channel.readInbound() == null);//true
        buf.release();
    }

}

該 testFramesDecoded()方法驗證了:一個包含 9 個可讀字節的 ByteBuf 被解碼爲 3 個 ByteBuf,每個都包含了 3 字節。需要注意的是,僅通過一次對 writeInbound()方法的調 用,ByteBuf 是如何被填充了 9 個可讀字節的。在此之後,通過執行 finish()方法,將 EmbeddedChannel 標記爲了已完成狀態。最後,通過調用 readInbound()方法,從 Embedded- Channel 中正好讀取了 3 個幀和一個 null。
testFramesDecoded2()方法也是類似的,只有一處不同:入站 ByteBuf 是通過兩個步 驟寫入的。當 writeInbound(input.readBytes(2))被調用時,返回了 false。爲什麼呢? 正如同上表中所描述的,如果對 readInbound()的後續調用將會返回數據,那麼 write- Inbound()方法將會返回 true。但是隻有當有 3 個或者更多的字節可供讀取時,FixedLengthFrameDecoder 纔會產生輸出。該測試剩下的部分和 testFramesDecoded()是相同的。

測試出站消息

測試出站消息的處理過程和剛纔所看到的類似。在下面的例子中,我們將會展示如何使用 EmbeddedChannel 來測試一個編碼器形式的 ChannelOutboundHandler,編碼器是一種 將一種消息格式轉換爲另一種的組件。

AbsIntegerEncoder,它是 Netty 的 MessageToMessageEncoder 的一個特殊化的實現,用於將負值整數轉換爲絕對值。

該示例將會按照下列方式工作:

  • 持有 AbsIntegerEncoder 的 EmbeddedChannel 將會以 4 字節的負整數的形式寫出站數據;
  • 編碼器將從傳入的 ByteBuf 中讀取每個負整數,並將會調用 Math.abs()方法來獲取其絕對值;
  • 編碼器將會把每個負整數的絕對值寫到 ChannelPipeline 中。
    在這裏插入圖片描述
public class AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf> {
    @Override
    protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        while (msg.readableBytes() >= 4) {
            //從輸入的ByteBuf中讀取下一個整數,並且計算其絕對值
            int value = Math.abs(msg.readInt());
            //將該整數寫入到編碼消息的List中
            out.add(value);
        }
    }
}

測試AbsIntegerEncoder:

public class AbsIntegerEncoderTest {
    @Test
    public void testEncoded() {
        ByteBuf buf = Unpooled.buffer();
        for (int i = 1; i < 10; i++) {
            buf.writeInt(i * -1);
        }
        //創建一個EmbeddedChanel,並安裝一個要測試的AbsIntegerEncoder
        EmbeddedChannel channel = new EmbeddedChannel(new AbsIntegerEncoder());
        //寫入ByteBuf,調用readOutbound()方法將會產生數據
        System.out.println(channel.writeOutbound(buf));
        System.out.println(channel.finish());

        channel.readOutbound();
        for (int i = 1; i < 10; i++) {
            int temp = channel.readOutbound();
            System.out.println(temp);
        }
        System.out.println(channel.readOutbound() == null);
    }
}

下面是代碼中執行的步驟。

  1. 將 4 字節的負整數寫到一個新的 ByteBuf 中。
  2. 創建一個 EmbeddedChannel,併爲它分配一個 AbsIntegerEncoder。
  3. 調用 EmbeddedChannel 上的 writeOutbound()方法來寫入該 ByteBuf。
  4. 標記該 Channel 爲已完成狀態。
  5. 從 EmbeddedChannel 的出站端讀取所有的整數,並驗證是否只產生了絕對值。

測試異常處理:

應用程序通常需要執行比轉換數據更加複雜的任務。例如,你可能需要處理格式不正確的輸 入或者過量的數據。在下一個示例中,如果所讀取的字節數超出了某個特定的限制,我們將會拋 出一個 TooLongFrameException。這是一種經常用來防範資源被耗盡的方法。

在下圖中,最大的幀大小已經被設置爲 3 字節。如果一個幀的大小超出了該限制,那麼程序將 會丟棄它的字節,並拋出一個 TooLongFrameException。位於 ChannelPipeline 中的其他 ChannelHandler 可以選擇在 exceptionCaught()方法中處理該異常或者忽略它:
在這裏插入圖片描述

//擴展ByteToMessageDecoder以將入站字節碼爲消息
public class FrameChunkDecoder extends ByteToMessageDecoder {
    private final int maxFrameSize;

    public FrameChunkDecoder(int maxFrameSize) {
        this.maxFrameSize = maxFrameSize;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int readableBytes = in.readableBytes();
        if (readableBytes > maxFrameSize) {
            //如果該幀太大,則丟棄它並拋出一個TooLongFrameException
            in.clear();
            throw new TooLongFrameException();
        }
        //否則,從ByteBuf中讀取一個新的幀
        ByteBuf buf = in.readBytes(readableBytes);
        //該幀添加到解碼消息的List中
        out.add(buf);
    }
}

測試FrameChunkDecoder:

public class FrameChunkDecoderTest {
    @Test
    public void testFramesDecoded() {
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < 9; i++) {
            buf.writeByte(i);
        }
        ByteBuf input = buf.duplicate();

        //創建一個EmbeddedChannel,並向其安裝一個幀大小爲3字節的FixedLengthFrameDecoder
        EmbeddedChannel channel = new EmbeddedChannel(new FrameChunkDecoder(3));

        System.out.println(channel.writeInbound(input.readBytes(2)));
        try {
            //寫入一個4字節大小的幀,並捕獲預期的異常
            channel.writeInbound(input.readBytes(4));
        } catch (TooLongFrameException e) {
            e.printStackTrace();
        }

        //寫入剩餘的2字節,會產生一個有效幀
        System.out.println(channel.writeInbound(input.readBytes(3)));//true
        System.out.println(channel.finish());

        //讀取產生的消息,並且驗證值
        ByteBuf read = channel.readInbound();
        System.out.println(read.equals(buf.readSlice(2)));//true
        read.release();

        read = channel.readInbound();
        System.out.println(read.equals(buf.skipBytes(4).readSlice(3)));//true
        read.release();
        buf.release();

    }
}

這裏使用的try/catch塊是EmbeddedChannel的一個特 殊功能。如果其中一個write*方法產生了一個受檢查的Exception,那麼它將會被包裝在一個 RuntimeException中並拋出。這使得可以容易地測試出一個Exception是否在處理數據的 過程中已經被處理了。

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