写点什么

netty 系列之:protobuf 在 UDP 协议中的使用

作者:程序那些事
  • 2022 年 4 月 27 日
  • 本文字数:2983 字

    阅读完需:约 10 分钟

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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 21 小时前阅读数: 16
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:protobuf在UDP协议中的使用_Java_程序那些事_InfoQ写作社区