簡介
字符串是我們程序中最常用到的消息格式,也是最簡單的消息格式,但是正因為字符串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之前還調(diào)用了LineBasedFrameDecoder,先把數(shù)據(jù)按行進行分割,然后再進行字符串的讀取。
那么有人要問了,decoder加入了LineBasedFrameDecoder預(yù)處理,為什么寫入的時候沒有添加行的分割符呢?
事實上這里有兩種處理方式,第一種就是在向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創(chuàng)建的文本文件,如果在unix下面打開的話,會發(fā)現(xiàn)每行后面多出了一個特殊字符,這是因為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,實際上是從系統(tǒng)屬性中獲取到的,如果沒有獲取到,則使用默認(rèn)的"\n"。
public static final String NEWLINE = SystemPropertyUtil.get("line.separator", "\n");
字符串編碼的實現(xiàn)
上面我們講到了和字符串編碼解碼相關(guān)的類分別是StringEncoder,LineEncoder和StringDecoder,我們來詳細看下這三個類的實現(xiàn)。
首先是StringEncoder,StringEncoder繼承了MessageToMessageEncoder:
public class StringEncoder extends MessageToMessageEncoder<CharSequence>
泛型中的CharSequence表示StringEncoder要encode的對象是CharSequence,也就是字符序列。
雖然大家常用String這個類,但是不一定大家都知道String其實是CharSequence的子類,所以StringEncoder也可以編碼字符串。
StringEncoder的編碼邏輯很簡單,將傳入的字符串msg轉(zhuǎn)換成為CharBuffer,然后調(diào)用ByteBufUtil的encodeString方法就可以轉(zhuǎn)換成為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參數(shù),用來預(yù)留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));
}
直接調(diào)用msg.toString方法即可將ByteBuf轉(zhuǎn)換成為字符串。
總結(jié)
以上就是netty中對字符串的編碼解碼器,通過使用這幾個編碼解碼器可以大大簡化我們的工作。
本文已收錄于 http://www.flydean.com/14-6-netty-codec-string/
最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發(fā)現(xiàn)!
歡迎關(guān)注我的公眾號:「程序那些事」,懂技術(shù),更懂你!