简介

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. 纯干货数学推导_傅里叶级数与傅里叶变换_Part4_傅里叶级数的复数形式

  2. 一文读懂充电宝usb接口电路及制作原理详细

    转自:http://www.elecfans.com/dianlutu/dianyuandianlu/20180511675801.html USB充电器套件,又名MP3/MP4充电器,输入AC160 ...

  3. AD软件Bug和自我失误的对战

    说说我近期犯的两大过失,让我无语的过失,要购买重大责任险呀 一大过失:上图,看了下面的图想必大家都明白了,TOP层元件只有位号和焊盘,丝印边框哪去了? 别急,在这里,下图 为何他跑这里来了呢?我尝试了 ...

  4. centos下用ffmpeg推流宇视科技摄像头rtsp流到前端播放(无flash)

    严禁垃圾中文技术网站复制粘贴 流程:安装SRS服务接收ffmpeg的推流,SRS会提供一个flv的播放地址,前端通过fls.js播放即可,无需flash. 1.安装ffmpeg 提供两个版本,都能推流 ...

  5. (SSM框架)实现小程序图片上传(配小程序源码)

    阅读本文约"2分钟" 又是一个开源小组件啦! 因为刚好做到这个小功能,所以就整理了一下,针对微信小程序的图片(文件)上传! 原业务是针对用户反馈的图片上传.(没错,本次还提供小程序 ...

  6. C#委托、多播委托极简案例,一看就懂

    废话不多讲,直接上代码,看完代码再讲解: class Class1 { public delegate void Del();//声明委托 public static void F1() { Cons ...

  7. C#编写一个控制台应用程序,可根据输入的月份判断所在季节

    编写一个控制台应用程序,可根据输入的月份判断所在季节 代码: using System; using System.Collections.Generic; using System.Linq; us ...

  8. HTML 和 form 表单常用标签

    HTML和CSS 常用标签: p:段落,自动换行 span:和div类似,但是默认不换行 br:换行 hr:分割线 h1-h6:标题标签 a:超链接 瞄点:通过给a链接设置#XX作为链接,给需要链接的 ...

  9. Springcloud报错:java.lang.IllegalStateException: Service id not legal hostname (/a-service)

    今天在做springcloud链路追踪的时候,报错java.lang.IllegalStateException: Service id not legal hostname (/a-service) ...

  10. IDM调用参数

    cmd调用IDM 基本命令: IDMan /d URL [/p 本地文件路径] [/f 本地文件名] [/q] [/h] [/n] [/a] 参数: 名称 作用 /d 下载文件的url /s 开始任务 ...