简介

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/

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

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

netty系列之:protobuf在UDP协议中的使用的更多相关文章

  1. Netty 系列九(支持UDP协议).

    一.基础知识 UDP 协议相较于 TCP 协议的特点: 1.无连接协议,没有持久化连接:2.每个 UDP 数据报都是一个单独的传输单元:3.一定的数据报丢失:4.没有重传机制,也不管数据报是否可达:5 ...

  2. TCP系列13—重传—3、协议中RTO计算和RTO定时器维护

    从上一篇示例中我们可以看到在TCP中有一个重要的过程就是决定何时进行超时重传,也就是RTO的计算更新.由于网络状况可能会受到路由变化.网络负载等因素的影响,因此RTO也必须跟随网络状况动态更新.如果T ...

  3. netty系列之:真正的平等–UDT中的Rendezvous

    目录 简介 建立支持Rendezvous的服务器 处理不同的消息 节点之间的交互 总结 简介 在我们之前提到的所有netty知识中,netty好像都被分为客户端和服务器端两部分.服务器端监听连接,并对 ...

  4. netty 3.9.2 UDP协议服务器和客户端DEMO

    说明:基于netty 3.9.2的udp协议实现的(如果你使用的版本是4.X或5.X,请参考其他方法):程序的逻辑结构是,客户端发送给服务端一串数据,服务器端返回给客户端“A”.在进行游戏开发时需要对 ...

  5. netty系列之:Bootstrap,ServerBootstrap和netty中的实现

    目录 简介 Bootstrap和ServerBootstrap的联系 AbstractBootstrap Bootstrap和ServerBootstrap 总结 简介 虽然netty很强大,但是使用 ...

  6. 网络编程(UDP协议-聊天程序)

    网络编程中的UDP协议中聊天程序,发送端口,和接受端口. 发送端口(Send): <span style="font-size:18px;">package cn.it ...

  7. netty系列之:使用UDP协议

    目录 简介 UDP协议 String和ByteBuf的转换 构建DatagramPacket 启动客户端和服务器 总结 简介 在之前的系列文章中,我们到了使用netty做聊天服务器,聊天服务器使用的S ...

  8. netty系列之: 在netty中使用 tls 协议请求 DNS 服务器

    目录 简介 支持DoT的DNS服务器 搭建支持DoT的netty客户端 TLS的客户端请求 总结 简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求 ...

  9. 计算机网络中的TCP/UDP协议到底是怎么回事(一)

    TCP/IP五层网络结构模型 物理层:物理层建立在物理通信介质的基础上,作为系统和通信介质的接口,用来实现数据链路实体间透明的比特 (bit) 流传输.只有该层为真实物理通信,其它各层为虚拟通信 数据 ...

随机推荐

  1. 一整套PCB设计流程和要点,老板再也不怕我出错!

    资料输入阶段 1. 在流程上接收到的资料是否齐全(包括:原理图.*.brd文件.料单.PCB设计说明以及PCB设计或更改要求.标准化要求说明.工艺设计说明文件) 2. 确认PCB模板是最新的 3. 确 ...

  2. C#和TS/JS的对比学习02:函数与方法

    程序本质上,就是由数据和处理数据的方法构成.函数和方法,这两个名词虽然字面不同,但意义上其实没有区别.只是因为它们出现的地方有异,给予了不同的名称,比如在全局环境中,叫函数,在对象或类中,叫方法.而C ...

  3. vue中事件冒泡规则和事件捕获规则

    <div id="app"> <div @click="handleClickOne"> <p @click="hand ...

  4. VSCode使用Settings Sync同步配置和插件

    转载参考地址:https://www.cnblogs.com/zzhaolei/p/12028241.html 1.需求 自己平常工作,一般在公司用公司的电脑,在家里就是自己的,但是vscode如果配 ...

  5. Golang | 并发

    goroutine 协程(Coroutine) Golang 在语言层面对并发编程进行了支持,使用了一种协程(goroutine)机制, 协程本质上是一种用户态线程,不需要操作系统来进行抢占式调度,但 ...

  6. Spring Boot-@ImportResource注解

    @ImportResource:导入Spring的配置文件,让配置文件里面的内容生效 第一步:创建一个spring配置文件bean.xml <?xml version="1.0&quo ...

  7. MySQL 集群历史版本信息

    MySQL 集群有两种命名方式,在Mysql5.1版本之前,MySQL 集群是以MySQL版本号命名:MySQL5.1(包括)之后开始以 mysql-mysql_server_version-ndb- ...

  8. 在 Docker 上快速运行 Apache Airflow 2.2.4

    Docker 安装 Apache Airflow 参考资料 Running Airflow in Docker 安装依赖 Docker Engine Docker Composite 快速运行 Apa ...

  9. GopherCon SG 2019 "Understanding Allocations" 学习笔记

    本篇是根据 GopherCon SG 2019 "Understanding Allocations" 演讲的学习笔记. Understanding Allocations: th ...

  10. 【课程汇总】OpenHarmony 成长计划知识赋能第三期系列课程(附链接)

    OpenHarmony 开源开发者成长计划第三期知识赋能课程硬核程度再次升级,带领开发者上手 OpenHarmony 的标准系统应用开发,学习标准设备应用开发,使用 eTS UI 开发分布式应用样例, ...