Netty實戰十之編解碼器框架
編碼和解碼,或者數據從一種特定協議的格式到另一種格式的轉換。這些任務將由通常稱為編解碼器的組件來處理。Netty提供了多種組件,簡化了為了支持廣泛的協議而創建自定義的編解碼器的過程。例如,如果你正在構建一個基於Netty的郵件伺服器,那麼你將會發現Netty對於編解碼器的支持對於實現POP3、IMAP和SMTP協議來說是多麼的寶貴。
1、什麼是編解碼器
每個網路應用程序都必須定義如何解析在兩個節點之間來回傳輸的原始位元組,以及如何將其和目標應用程序的數據格式做相互轉換。這種轉換邏輯由編解碼器處理,編解碼器有編碼器和解碼器組成,它們每種都可以將位元組流從一種格式轉換為另一種格式。
如果將消息看作是對於特定的應用程序具有具體含義的結構化的位元組序列——它的數據。那麼編碼器是將消息轉換為適合於傳輸的格式(最有可能的就是位元組流);而對應的解碼器則是將網路位元組流轉換回應用程序的消息格式。因此,編碼器操作出站數據,而解碼器處理入站數據。
2、解碼器
——將位元組解碼為消息——ByteToMessageDecoder和ReplayingDecoder
——將一種消息類型解碼為另一種——MessageToMessageDecoder
因為解碼器是負責將入站數據從一種格式轉換到另一種格式的,所以知道Netty的解碼器實現了ChannelInboundHandler也不會讓你感到意外。
每當需要為ChannelPipeline中的下一個ChannelInboundHandler轉換入站數據時會用到。此外,得益於ChannelPipeline的設計,可以將多個解碼器鏈接在一起,以實現任意複雜的轉換邏輯,這也是Netty是如何支持代碼的模塊化以及復用的例子。
3、抽象類ByteToMessageDecoder
將位元組解碼為消息是一項如此常見的任務,以至於Netty為他提供了一個抽象的基類:ByteToMessageDecoder。由於你不可能知道遠程節點是否會一次性地發送一個完整的消息,所以這個類會對入站數據進行緩衝。
下面舉一個如何使用這個類的示例,假設你接收了一個包含簡單int的位元組流,每個int都需要被單獨處理。在這種情況下,你需要從入站ByteBuf中讀取每個int,並將它傳遞給ChannelPipeline中的下一個ChannelInboundHandler。為了解碼這個位元組流,你要擴展ByteToMessageDecoder類。(需要注意的是,原始類型int在被添加到List中時,會被自動裝箱為Integer),如下設計圖。每次從入站ByteBuf中讀取4位元組,將其解碼為一個Int,然後將它添加到一個List中。當沒有更多的元素可以被添加到該List中時,它的內容將會被發送給下一個ChannelInboundHandler。
雖然ByteToMessageDecoder使得可以很簡單地實現這種模式,但是你可能會發現,在調用readint()方法前不得不驗證所輸入的ByteBuf是否具有足夠的數據有點繁瑣。
4、抽象類ReplayingDecoder
ReplayingDecoder擴展了ByteToMessageDecoder類,使得我們不必調用readableBytes()方法。它通過使用一個自定義的ByteBuf實現,ReplayingDecoderByteBuf,包裝傳入的ByteBuf實現了這一點,其將在內部執行該調用。
public abstract class ReplayingDecoder extends ByteToMessageDecoder
類型參數S指定了用於狀態管理的類型,其中Void代表不需要狀態管理。以下代碼展示了基於ReplayingDecoder重新實現ToIntegerDecoder。
和之前一樣,從ByteBuf中提取的int將會被添加到List中,如果沒有足夠的位元組可用,這個readInt()方法的實現將會拋出一個Error,其將在基類中被捕獲並處理。當有更多的數據可供讀取時,該decode()方法將會被再次調用。
請注意ReplayingDecoderByteBuf的下面這些方面:
——並不是所有的ByteBuf操作都被支持,如果調用了一個不被支持的方法,將會拋出一個UnsupportedOperationException
——ReplayingDecoder稍慢於ByteToMessageDecoder
——如果使用ByteToMessageDecoder不會引入太多的複雜性,那麼請使用它;否則,請使用ReplayingDecoder
5、抽象類MessageToMessageDecoder
public abstract class MessageToMessageDecoderextends ChannelInboundHandlerAdapter
類型參數I指定了decode()方法的輸入參數msg的類型,它是你必須實現的唯一方法。
在這個示例中,我們將編寫一個IntegerToStringDecoder解碼器來擴展MessageToMessageDecoder。它的decode()方法會把Integer參數轉換為它的String表示,並將擁有下列簽名:
public void decode( ChannelHandlerContext ctx, Integer msg , List out) throws Exception
和之前一樣,解碼的String將被添加到傳出的List中,並轉發給下一個ChannelInboundHandler
6、TooLongFrameException類
由於Netty是一個非同步框架,所以需要在位元組可以解碼之前在內存中緩衝他們,因此,不能讓解碼器緩衝大量的數據以至於耗盡可用的內存。為了解除這個常見的顧慮,Netty提供了TooLongFrameException類,其將由解碼器在幀超出指定的大小限制時拋出。
為了避免這種情況,你可以設置一個最大位元組數的伐值,如果超過,則會導致拋出一個TooLongFrameException,如何處理該異常則完全取決於解碼器的用戶。某些協議(HTTP)可能允許你返回一個特殊的響應,而在其他的情況下,唯一的選擇可能就是關閉對應的連接。
以下代碼展示了ByteToMessageDecoder是如何使用TooLongFrameException來通知ChannelPipeline中的其他ChannelHandler發生了幀大小溢出的。需要注意的是,如果你正在使用一個可變幀大小的協議,那麼這種保護措施將是尤其重要的。
7、抽象類MessageToByteEncoder
這個類只有一個方法,而解碼器有兩個。原因是解碼器通常需要在Channel關閉之後產生最後一個消息(decodeLast()方法),這顯然不適用於編碼器的場景——在連接關閉之後仍然產生一個消息是毫無意義的。
ShortToByteEncoder,其接受一個Short類型的實例作為消息,將它編碼為Short的原始類型值,並將它寫入ByteBuf中,其將隨後被轉發給ChannelPipeline中的下一個CHannelOutboundHandler。每個傳出的Short值都將會佔用ByteBuf中的2位元組。
8、抽象類MessageToMessageEncoder
我們將展示對於出站數據將如何從一種消息編碼為另一種。MessageToMessageEncoder類的encoder()方法提供了這種能力。
以下代碼,編碼器將每個出站Integer的String表示添加到了該List中。
9、抽象的編解碼器類
在同一個類中管理入站和出站數據和消息的轉換是很有用的。Netty的抽象編解碼器類正好用於這個目的,因為它們每個都將捆綁一個解碼器/編碼器對,以處理我們一直在學習的這兩種類型的操作。
通過儘可能地將這兩種功能分開,最大化了代碼的可重用性和可擴展性,這是Netty設計的一個基本原則。
10、抽象類ByteToMessageCodec
場景:我們需要將位元組編碼為某種形式的消息,可能是POJO,隨後再次對它進行編碼。ByteToMessageCodec將為我們處理好了這一切,因為它結合了ByteToMessageDecoder以及他的逆向MessageToByteEncoder。
任何的請求/響應協議都可以作為使用ByteToMessageCodec的理想選擇,例如,在某個SMTP的實現中,編解碼器將讀取傳入位元組,並將它們解碼為一個自定義的消息類型,如SmtpRequest。而在接收端,當一個響應被創建時,將會產生一個SmtpResponse,其將被編碼回位元組以便進行傳輸。
11、CombinedChannelDuplexHandler類
結合一個解碼器和編碼器可能會對可重用性造成影響。但是,有一種方法即能夠避免這種懲罰,又不會犧牲將一個解碼器和一個編碼器作為一個單獨的單元部署所帶來的的便利性。CombinedChannelDuplexHandler提供了這個解決方案,其聲明為:
public class CombinedChannelDuplexHandler
這個類充當了ChannelInboundHandler和ChannelOutboundHandler(該類的類型參數I和O)的容器。通過提供分別繼承了解碼器類和編碼器類的類型,我們可以實現一個編解碼器,而又不必直接擴展抽象的編解碼器類。
首先,讓我們研究代碼中的ByteToCharDecoder。注意,該實現擴展了ByteToMessageDecoder,因為它要從ByteBuf中讀取字元。
這裡的decode()方法一次將從ByteBuf中提取2位元組,並將它們作為char寫入到List中,其將會被自動裝箱為Character對象。
以下代碼,包含了CharToByteEncoder,他能將Character轉換回位元組。這個類擴展了MessageToByteEncoder,因為它需要將char消息編碼到ByteBuf中,這是通過直接寫入ByteBuf做到的。
既然我們有了解碼器和編碼器,我們將會結合它們來構建 一個編解碼器。如以下代碼所示。
在某些情況下,通過這種方式結合實現相對於使用編解碼器類的方式來說可能更加的簡單也更加的靈活。
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
※旅途愉快——基本餐飲禮儀
※精準引流營銷:精準客戶QQ轟炸技術
TAG:全球大搜羅 |