高性能IO框架Netty五 - Netty內置的編解碼器

目錄

 一、什麼是編解碼器

二、解碼器

2.1ByteToMessageDecoder 

2.2 MessageToMessageDecoder

2.3 TooLongFrameException

三、編碼器

3.1 MessageToByteEncoder 

3.2 MessageToMessageEncoder

四、編解碼器類

五、Netty內置的編解碼器和ChannelHandler

5.1 通過SSL/TLS 保護Netty 應用程序

5.2 HTTP 系列

5.2.1 Neety內置Http相關解碼器

5.2.2 聚合HTTP 消息

5.2.3 HTTP 壓縮

5.2.4 使用HTTPS

5.2.5 WebSocket

5.2.6 空閒的連接和超時


emm,最近Netty篇章比較幹,沒用過的人估計很晦澀難懂,想寫的具體生動一些,怎奈文筆有限。如果只想知道具體作用的話,堅持,馬上就到實戰環節了!

 

 一、什麼是編解碼器

就像我們用的Spring裏面的HttpRequest對象,難道客戶端傳過來就是一個HttpRequest對象嗎?很顯然一般網絡中傳輸都是通過字節流進行傳輸的。那麼由字節流轉爲我們需要的對象的過程就叫做解碼。從對象再轉爲字節流或者其他對象的過程,叫做編碼。

每個網絡應用程序都必須定義如何解析在兩個節點之間來回傳輸的原始字節,以及如何將其和目標應用程序的數據格式做相互轉換。這種轉換邏輯由編解碼器處理,編解碼器由編碼器和解碼器組成,它們每種都可以將字節流從一種格式轉換爲另一種格式。那麼它們的區別是什麼呢?

如果將消息看作是對於特定的應用程序具有具體含義的結構化的字節序列—它的數據。那麼編碼器是將消息轉換爲適合於傳輸的格式(最有可能的就是字節流);而對應的解碼器則是將網絡字節流轉換回應用程序的消息格式。因此,編碼器操作出站數據,而解碼器處理入站數據。

 

我們前面所學的解決粘包半包的其實也是編解碼器框架的一部分,例如下圖,就是個定長的解碼器。

二、解碼器

Netty中,解碼器一般有兩種,如下

將字節解碼爲消息——ByteToMessageDecoder,類關係圖如下

將一種消息類型解碼爲另一種——MessageToMessageDecoder,類關係圖如下

因爲解碼器是負責將入站數據從一種格式轉換到另一種格式的,所以Netty 的解碼器實現了ChannelInboundHandler。

什麼時候會用到解碼器呢?

很簡單:每當需要爲ChannelPipeline 中的下一個Channel-InboundHandler 轉換入站數據時會用到。此外,得益於ChannelPipeline 的設計,可以將多個解碼器鏈接在一起,以實現任意複雜的轉換邏輯。

2.1ByteToMessageDecoder 

抽象類ByteToMessageDecoder 將字節解碼爲消息

將字節解碼爲消息(或者另一個字節序列)是一項如此常見的任務,以至於Netty 爲它提供了一個抽象的基類:ByteToMessageDecoder。由於你不可能知道遠程節點是否會一次性地發送一個完整的消息,所以這個類會對入站數據進行緩衝,直到它準備好處理。

它最重要方法

decode(ChannelHandlerContext ctx,ByteBuf in,List<Object> out)

這是你必須實現的唯一抽象方法。decode()方法被調用時將會傳入一個包含了傳入數據的ByteBuf,以及一個用來添加解碼消息的List。對這個方法的調用將會重複進行,直到確定沒有新的元素被添加到該List,或者該ByteBuf 中沒有更多可讀取的字節時爲止。然後,如果該List 不爲空,那麼它的內容將會被傳遞給ChannelPipeline 中的下一個ChannelInboundHandler。

代理示例如下圖,實現一個定長處理的消息解碼器

/**
 * DrakKing
 */
//擴展 ByteToMessageDecoder 以處理入站字節,並將它們解碼爲消息
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);
        }
    }
}

2.2 MessageToMessageDecoder

抽象類MessageToMessageDecoder 將一種消息類型解碼爲另一種,在兩個消息格式之間進行轉換(例如,從String->Integer)

主要方法爲

decode(ChannelHandlerContext ctx,I msg,List<Object> out)

對於每個需要被解碼爲另一種格式的入站消息來說,該方法都將會被調用。解碼消息隨後會被傳遞給ChannelPipeline中的下一個ChannelInboundHandler

MessageToMessageDecoder<T>,T代表源數據的類型

2.3 TooLongFrameException

由於Netty 是一個異步框架,所以需要在字節可以解碼之前在內存中緩衝它們。因此,不能讓解碼器緩衝大量的數據以至於耗盡可用的內存。爲了解除這個常見的顧慮,Netty 提供了TooLongFrameException 類,其將由解碼器在幀超出指定的大小限制時拋出。

爲了避免這種情況,你可以設置一個最大字節數的閾值,如果超出該閾值,則會導致拋出一個TooLongFrameException(隨後會被ChannelHandler.exceptionCaught()方法捕獲)。然後,如何處理該異常則完全取決於該解碼器的用戶。某些協議(如HTTP)可能允許你返回一個特殊的響應。而在其他的情況下,唯一的選擇可能就是關閉對應的連接。

三、編碼器

解碼器的功能正好相反。Netty 提供了一組類,用於幫助你編寫具有以下功能的編碼器:

將消息編碼爲字節;MessageToByteEncoder,

將消息編碼爲消息:MessageToMessageEncoder<T>,T代表源數據的類型,類關係圖如下

3.1 MessageToByteEncoder 

抽象類 MessageToByteEncoder 將消息編碼爲字節

encode(ChannelHandlerContext ctx,I msg,ByteBuf out)

encode()方法是你需要實現的唯一抽象方法。它被調用時將會傳入要被該類編碼爲ByteBuf 的(類型爲I 的)出站消息。該ByteBuf 隨後將會被轉發給ChannelPipeline中的下一個ChannelOutboundHandler

代碼示例

/**
 * DarkKing
 * 類說明:序列化機制
 */
public class MsgPackEncode extends MessageToByteEncoder<Object> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg,
                          ByteBuf out) throws Exception {
        MessagePack messagePack = new MessagePack();
        byte[] raw = messagePack.write(msg);
        out.writeBytes(raw);
    }
}

3.2 MessageToMessageEncoder

抽象類 MessageToMessageEncoder 將消息編碼爲消息

encode(ChannelHandlerContext ctx,I msg,List<Object> out)

這是你需要實現的唯一方法。每個通過write()方法寫入的消息都將會被傳遞給encode()方法,以編碼爲一個或者多個出站消息。隨後,這些出站消息將會被轉發給ChannelPipeline中的下一個ChannelOutboundHandler

代碼示例

/**
 * DarkKing
 */
//擴展 MessageToMessageEncoder 以將一個消息編碼爲另外一種格式
public class AbsIntegerEncoder extends
        MessageToMessageEncoder<ByteBuf> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,
                          ByteBuf in, List<Object> out) throws Exception {
        //檢查是否有足夠的字節用來編碼,int爲4個字節
        while (in.readableBytes() >= 4) {
            //從輸入的 ByteBuf中讀取下一個整數,並且計算其絕對值
            int value = Math.abs(in.readInt());
            //將該整數寫入到編碼消息的 List 中
            out.add(value);
        }
    }
}

四、編解碼器類

我們一直將解碼器和編碼器作爲單獨的實體討論,但是你有時將會發現在同一個類中管理入站和出站數據和消息的轉換是很有用的。Netty 的抽象編解碼器類正好用於這個目的,因爲它們每個都將捆綁一個解碼器/編碼器對,以處理我們一直在學習的這兩種類型的操作。這些類同時實現了ChannelInboundHandler 和ChannelOutboundHandler 接口。

爲什麼我們並沒有一直優先於單獨的解碼器和編碼器使用這些複合類呢?因爲通過儘可能地將這兩種功能分開,最大化了代碼的可重用性和可擴展性,這是Netty 設計的一個基本原則。

相關的類:

抽象類ByteToMessageCodec

抽象類MessageToMessageCodec

五、Netty內置的編解碼器和ChannelHandler

Netty 爲許多通用協議提供了編解碼器和處理器,幾乎可以開箱即用,這減少了你在那些相當繁瑣的事務上本來會花費的時間與精力。

5.1 通過SSL/TLS 保護Netty 應用程序

SSL和TLS這樣的安全協議,它們層疊在其他協議之上,用以實現數據安全。我們在訪問安全網站時遇到過這些協議,但是它們也可用於其他不是基於HTTP的應用程序,如安全SMTP(SMTPS)郵件服務器甚至是關係型數據庫系統。

爲了支持SSL/TLS,Java 提供了javax.net.ssl 包,它的SSLContext 和SSLEngine類使得實現解密和加密相當簡單直接。Netty 通過一個名爲SslHandler 的ChannelHandler實現利用了這個API,其中SslHandler 在內部使用SSLEngine 來完成實際的工作。

Netty 還提供了使用OpenSSL 工具包(www.openssl.org)的SSLEngine 實現。這個OpenSsl-Engine 類提供了比JDK 提供的SSLEngine 實現更好的性能。

如果OpenSSL庫可用,可以將Netty 應用程序(客戶端和服務器)配置爲默認使用OpenSslEngine。如果不可用,Netty 將會回退到JDK 實現。

在大多數情況下,SslHandler 將是ChannelPipeline 中的第一個ChannelHandler。

例如,給自己的應用添加SSL功能,只需添加SSL相關的handler即可。

5.2 HTTP 系列

HTTP 是基於請求/響應模式的:客戶端向服務器發送一個HTTP 請求,然後服務器將會返回一個HTTP 響應。Netty 提供了多種編碼器和解碼器以簡化對這個協議的使用。

5.2.1 Neety內置Http相關解碼器

一個HTTP 請求/響應可能由多個數據部分組成,並且它總是以一個LastHttpContent 部分作爲結束。FullHttpRequest 和FullHttpResponse 消息是特殊的子類型,分別代表了完整的請求和響應。所有類型的HTTP 消息(FullHttpRequest、LastHttpContent等等)都實現了HttpObject 接口。

HttpRequestEncoder 將HttpRequest、HttpContent 和LastHttpContent 消息編碼爲字節

HttpResponseEncoder 將HttpResponse、HttpContent 和LastHttpContent 消息編碼爲字節

HttpRequestDecoder 將字節解碼爲HttpRequest、HttpContent 和LastHttpContent 消息

HttpResponseDecoder 將字節解碼爲HttpResponse、HttpContent 和LastHttpContent 消息

5.2.2 聚合HTTP 消息

由於HTTP 的請求和響應可能由許多部分組成,因此你需要聚合它們以形成完整的消息。爲了消除這項繁瑣的任務,Netty 提供了一個聚合器,它可以將多個消息部分合併爲FullHttpRequest 或者FullHttpResponse 消息。通過這樣的方式,你將總是看到完整的消息內容。

5.2.3 HTTP 壓縮

當使用HTTP 時,建議開啓壓縮功能以儘可能多地減小傳輸數據的大小。雖然壓縮會帶來一些CPU 時鐘週期上的開銷,但是通常來說它都是一個好主意,特別是對於文本數據來說。Netty 爲壓縮和解壓縮提供了ChannelHandler 實現,它們同時支持gzip 和deflate 編碼。

5.2.4 使用HTTPS

啓用HTTPS 只需要將SslHandler 添加到ChannelPipeline 的ChannelHandler 組合中。

SSL和HTTP的代碼參見模塊netty-http

5.2.5 WebSocket

後面單獨當做應用來講

5.2.6 空閒的連接和超時

檢測空閒連接以及超時對於及時釋放資源來說是至關重要的。由於這是一項常見的任務,Netty 特地爲它提供了幾個ChannelHandler 實現。

IdleStateHandler 當連接空閒時間太長時,將會觸發一個IdleStateEvent 事件。然後,你可以通過在你的ChannelInboundHandler 中重寫userEventTriggered()方法來處理該IdleStateEvent 事件。

ReadTimeoutHandler 如果在指定的時間間隔內沒有收到任何的入站數據,則拋出一個Read-TimeoutException 並關閉對應的Channel。可以通過重寫你的ChannelHandler 中的exceptionCaught()方法來檢測該Read-TimeoutException。

WriteTimeoutHandler 如果在指定的時間間隔內沒有任何出站數據寫入,則拋出一個Write-TimeoutException 並關閉對應的Channel 。可以通過重寫你的ChannelHandler 的exceptionCaught()方法檢測該WriteTimeout-Exception。

 

到這裏,Netty的一些基礎理論知識差不多就講完了。後面開始準備進入Netty的實戰環節。針對Netty實現相關的一些應用。加深對Netty組建的一些瞭解。

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