netty系列之:protobuf在UDP協議中的使用
簡介
netty中提供的protobuf編碼解碼器可以讓我們直接在netty中傳遞protobuf物件。同時netty也提供了支援UDP協議的channel叫做NioDatagramChannel。如果直接使用NioDatagramChannel,那麼我們可以直接從channel中讀寫UDP物件:DatagramPacket。
但是DatagramPacket中封裝的是ByteBuf物件,如果我們想要向UDP channel中寫入物件,那麼需要一個將物件轉換成為ByteBuf的方法,很明顯netty提供的protobuf編碼解碼器就是一個這樣的方法。
那麼可不可以將NioDatagramChannel和ProtobufDecoder,ProtobufEncoder相結合呢?
NioDatagramChannel中channel讀寫的物件都是DatagramPacket。而ProtobufDecoder與ProtobufEncoder是將protoBuf物件MessageLiteOrBuilder跟ByteBuf進行轉換,所以兩者是不能直接結合使用的。
怎麼才能在UDP中使用protobuf呢?今天要向大家介紹netty專門為UDP建立的編碼解碼器DatagramPacketEncoder和DatagramPacketDecoder。
UDP在netty中的表示
UDP的資料包在netty中是怎麼表示呢?
netty提供了一個類DatagramPacket來表示UDP的資料包。netty中的UDP channel就是使用DatagramPacket來進行資料的傳遞。先看下DatagramPacket的定義:
public class DatagramPacket
extends DefaultAddressedEnvelope<ByteBuf, InetSocketAddress> implements ByteBufHolder
DatagramPacket繼承自DefaultAddressedEnvelope,並且實現了ByteBufHolder介面。
其中的ByteBuf是資料包中需要傳輸的資料,InetSocketAddress是資料包需要傳送到的地址。
而這個DefaultAddressedEnvelope又是繼承自AddressedEnvelope:
public class DefaultAddressedEnvelope<M, A extends SocketAddress> implements AddressedEnvelope<M, A>
DefaultAddressedEnvelopee中有三個屬性,分別是message,sender和recipient:
private final M message;
private final A sender;
private final A recipient;
這三個屬性分別代表了要傳送的訊息,傳送方的地址和接收方的地址。
DatagramPacketEncoder
DatagramPacketEncoder是一個DatagramPacket的編碼器,所以要編碼的物件就是DatagramPacket。上一節我們也提到了DatagramPacket實際上繼承自AddressedEnvelope。所有的DatagramPacket都是一個AddressedEnvelope物件,所以為了通用起見,DatagramPacketEncoder接受的要編碼的物件是AddressedEnvelope。
我們先來看下DatagramPacketEncoder的定義:
public class DatagramPacketEncoder<M> extends MessageToMessageEncoder<AddressedEnvelope<M, InetSocketAddress>> {
DatagramPacketEncoder是一個MessageToMessageEncoder,它接受一個AddressedEnvelope的泛型,也就是我們要encoder的物件型別。
那麼DatagramPacketEncoder會將AddressedEnvelope編碼成什麼呢?
DatagramPacketEncoder中定義了一個encoder,這個encoder可以在DatagramPacketEncoder初始化的時候傳入:
private final MessageToMessageEncoder<? super M> encoder;
public DatagramPacketEncoder(MessageToMessageEncoder<? super M> encoder) {
this.encoder = checkNotNull(encoder, "encoder");
}
實際上DatagramPacketEncoder中實現的encode方法,底層就是呼叫encoder的encode方法,我們來看下他的實現:
protected void encode(
ChannelHandlerContext ctx, AddressedEnvelope<M, InetSocketAddress> msg, List<Object> out) throws Exception {
assert out.isEmpty();
encoder.encode(ctx, msg.content(), out);
if (out.size() != 1) {
throw new EncoderException(
StringUtil.simpleClassName(encoder) + " must produce only one message.");
}
Object content = out.get(0);
if (content instanceof ByteBuf) {
// Replace the ByteBuf with a DatagramPacket.
out.set(0, new DatagramPacket((ByteBuf) content, msg.recipient(), msg.sender()));
} else {
throw new EncoderException(
StringUtil.simpleClassName(encoder) + " must produce only ByteBuf.");
}
}
上面的邏輯就是從AddressedEnvelope中呼叫msg.content()
方法拿到AddressedEnvelope中的內容,然後呼叫encoder的encode方法將其編碼並寫入到out中。
最後呼叫out的get方法拿出編碼之後的內容,再封裝到DatagramPacket中去。
所以不管encoder最後返回的是什麼物件,最後都會被封裝到DatagramPacket中,並返回。
總結一下,DatagramPacketEncoder傳入一個AddressedEnvelope物件,呼叫encoder將AddressedEnvelope的內容進行編碼,最後封裝成為一個DatagramPacket並返回。
鑑於protoBuf的優異物件序列化能力,我們可以將ProtobufEncoder傳入到DatagramPacketEncoder中,做為真實的encoder:
ChannelPipeline pipeline = ...;
pipeline.addLast("udpEncoder", new DatagramPacketEncoder(new ProtobufEncoder(...));
這樣就把ProtobufEncoder和DatagramPacketEncoder結合起來了。
DatagramPacketDecoder
DatagramPacketDecoder是和DatagramPacketEncoder相反的操作,它是將接受到的DatagramPacket物件進行解碼,至於解碼成為什麼物件,也是由傳入其中的decoder屬性來決定的:
public class DatagramPacketDecoder extends MessageToMessageDecoder<DatagramPacket> {
private final MessageToMessageDecoder<ByteBuf> decoder;
public DatagramPacketDecoder(MessageToMessageDecoder<ByteBuf> decoder) {
this.decoder = checkNotNull(decoder, "decoder");
}
DatagramPacketDecoder要解碼的物件是DatagramPacket,而傳入的decoder要解碼的物件是ByteBuf。
所以我們需要一個能夠解碼ByteBuf的decoder實現,而和protoBuf對應的就是ProtobufDecoder。
先來看下DatagramPacketDecoder的decoder方法是怎麼實現的:
protected void decode(ChannelHandlerContext ctx, DatagramPacket msg, List<Object> out) throws Exception {
decoder.decode(ctx, msg.content(), out);
}
可以看到DatagramPacketDecoder的decoder方法很簡單,就是從DatagramPacket中拿到content內容,然後交由decoder去decode。
如果使用ProtobufDecoder作為內建的decoder,則可以將ByteBuf物件decode成為ProtoBuf物件,剛好和之前講過的encode相呼應。
將ProtobufDecoder傳入DatagramPacketDecoder也非常簡單,我們可以這樣做:
ChannelPipeline pipeline = ...;
pipeline.addLast("udpDecoder", new DatagramPacketDecoder(new ProtobufDecoder(...));
這樣一個DatagramPacketDecoder就完成了。
總結
可以看到,如果直接使用DatagramPacketEncoder和DatagramPacketDecoder加上ProtoBufEncoder和ProtoBufDecoder,那麼實現的是DatagramPacket和ByteBuf直接的互相轉換。
當然這裡的ProtoBufEncoder和ProtoBufDecoder可以按照使用者的需要被替換成為不同的編碼解碼器。
可以自由組合編碼解碼方式,就是netty編碼器的最大魅力。
本文已收錄於 http://www.flydean.com/17-1-netty-protobuf-udp/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!