簡介
字串是我們程式中最常用到的訊息格式,也是最簡單的訊息格式,但是正因為字串string太過簡單,不能附加更多的資訊,所以在netty中選擇的是使用byteBuf作為最底層的訊息傳遞載體。
雖然底層使用的ByteBuf,但是對於程式設計師來說,還是希望能夠使用這種最簡單的字串格式,那麼有什麼簡單的方法嗎?
netty中的字串編碼解碼器
為了解決在netty的channel中傳遞字串的問題,netty提供了針對於字串的編碼和解碼器,分別是StringEncoder和StringDecoder。
我們來看下他們是怎麼在程式中使用的,首先是將StringDecoder和StringEncoder加入channelPipeline中:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
// Encoder
pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
注意,這裡我們在使用StringDecoder之前還呼叫了LineBasedFrameDecoder,先把資料按行進行分割,然後再進行字串的讀取。
那麼有人要問了,decoder加入了LineBasedFrameDecoder預處理,為什麼寫入的時候沒有新增行的分割符呢?
事實上這裡有兩種處理方式,第一種就是在向channel中寫入字串的時候,手動加上行分隔符,如下所示:
void channelRead(ChannelHandlerContext ctx, String msg) {
ch.write("Did you say '" + msg + "'?\n");
}
如果不想每次都在msg後面加上換行符,那麼可以將StringEncoder替換成為LineEncoder,上面的pipeline就變成下面這樣:
ChannelPipeline pipeline = ...;
// Decoders
pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(80));
pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
// Encoder
pipeline.addLast("lineEncoder", new LineEncoder(LineSeparator.UNIX, CharsetUtil.UTF_8));
這樣,我們在handler中就不需要手動新增換行符了,如下所示:
void channelRead(ChannelHandlerContext ctx, String msg) {
ch.write("Did you say '" + msg + "'?");
}
不同平臺的換行符
在unix和windows平臺傳遞過文字檔案的朋友可能會遇到一個問題,就是windows建立的文字檔案,如果在unix下面開啟的話,會發現每行後面多出了一個特殊字元,這是因為unix和windows平臺定義的換行符是不同的。
在unix平臺通常使用"\n"來換行,而在windows平臺則使用""\r\n"來換行。
java程式因為是跨平臺的,寫出的程式可能執行在unix平臺,也可能執行在windows平臺,所以我們需要有一個辦法來獲取平臺的換行符,netty提供了一個LineSeparator的類來完成這個工作。
LineSeparator中有三個換行符的定義,分別是:
public static final LineSeparator DEFAULT = new LineSeparator(StringUtil.NEWLINE);
public static final LineSeparator UNIX = new LineSeparator("\n");
public static final LineSeparator WINDOWS = new LineSeparator("\r\n");
UNIX和WINDOWS很好理解,他們就是我們剛剛講到的不同的平臺。
那麼什麼是DEFAULT呢?DEFAULT中傳入的NEWLINE,實際上是從系統屬性中獲取到的,如果沒有獲取到,則使用預設的"\n"。
public static final String NEWLINE = SystemPropertyUtil.get("line.separator", "\n");
字串編碼的實現
上面我們講到了和字串編碼解碼相關的類分別是StringEncoder,LineEncoder和StringDecoder,我們來詳細看下這三個類的實現。
首先是StringEncoder,StringEncoder繼承了MessageToMessageEncoder:
public class StringEncoder extends MessageToMessageEncoder<CharSequence>
泛型中的CharSequence表示StringEncoder要encode的物件是CharSequence,也就是字元序列。
雖然大家常用String這個類,但是不一定大家都知道String其實是CharSequence的子類,所以StringEncoder也可以編碼字串。
StringEncoder的編碼邏輯很簡單,將傳入的字串msg轉換成為CharBuffer,然後呼叫ByteBufUtil的encodeString方法就可以轉換成為ByteBuf,並加入out中去:
protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
if (msg.length() == 0) {
return;
}
out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
}
LineEncoder和StringEncoder很類似,它也是繼承自MessageToMessageEncoder:
public class LineEncoder extends MessageToMessageEncoder<CharSequence>
不同之處在於encoder方法:
protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
ByteBuf buffer = ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset, lineSeparator.length);
buffer.writeBytes(lineSeparator);
out.add(buffer);
}
ByteBufUtil的encodeString多了一個lineSeparator.length引數,用來預留lineSeparator的位置,然後在返回的ByteBuf後面加上lineSeparator作為最終的輸出。
StringDecoder是和StringEncoder相反的過程:
public class StringDecoder extends MessageToMessageDecoder<ByteBuf>
這裡的ByteBuf表示的是要解碼的物件是ByteBuf,我們看下他的解碼方法:
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
out.add(msg.toString(charset));
}
直接呼叫msg.toString方法即可將ByteBuf轉換成為字串。
總結
以上就是netty中對字串的編碼解碼器,通過使用這幾個編碼解碼器可以大大簡化我們的工作。
本文已收錄於 http://www.flydean.com/14-6-netty-codec-string/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!