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

简介: netty中提供的protobuf编码解码器可以让我们直接在netty中传递protobuf对象。同时netty也提供了支持UDP协议的channel叫做NioDatagramChannel。如果直接使用NioDatagramChannel,那么我们可以直接从channel中读写UDP对象:DatagramPacket。

简介

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/

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

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

相关文章
|
3月前
|
存储 网络协议 算法
UDP 协议和 TCP 协议
本文介绍了UDP和TCP协议的基本结构与特性。UDP协议具有简单的报文结构,包括报头和载荷,报头由源端口、目的端口、报文长度和校验和组成。UDP使用CRC校验和来检测传输错误。相比之下,TCP协议提供更可靠的传输服务,其结构复杂,包含序列号、确认序号和标志位等字段。TCP通过确认应答和超时重传来保证数据传输的可靠性,并采用三次握手建立连接,四次挥手断开连接,确保通信的稳定性和完整性。
104 1
UDP 协议和 TCP 协议
|
9天前
|
存储 网络协议 安全
用于 syslog 收集的协议:TCP、UDP、RELP
系统日志是从Linux/Unix设备及网络设备生成的日志,可通过syslog服务器集中管理。日志传输支持UDP、TCP和RELP协议。UDP无连接且不可靠,不推荐使用;TCP可靠,常用于rsyslog和syslog-ng;RELP提供可靠传输和反向确认。集中管理日志有助于故障排除和安全审计,EventLog Analyzer等工具可自动收集、解析和分析日志。
|
24天前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
53 3
|
5月前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
300 2
|
1月前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
40 4
|
3月前
|
网络协议
UDP 协议
UDP 协议
141 58
|
2月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
52 1
|
2月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
170 3
|
2月前
|
存储 网络协议 算法
更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】
UDP和TCP各有所长,UDP以其低延迟、轻量级的特点适用于对实时性要求极高的应用,而TCP凭借其强大的错误检测、流量控制和拥塞控制机制,确保了数据的可靠传输,适用于文件传输、网页浏览等场景。理解它们的工作原理,特别是UDP的缓冲区管理和TCP的8种策略,对于优化网络应用的性能、确保数据的高效和可靠传输至关重要。开发者在选择传输层协议时,应根据实际需求权衡利弊,合理利用这两项关键技术。
87 5
|
2月前
|
JavaScript 安全 Java
谈谈UDP、HTTP、SSL、TLS协议在java中的实际应用
下面我将详细介绍UDP、HTTP、SSL、TLS协议及其工作原理,并提供Java代码示例(由于Deno是一个基于Node.js的运行时,Java代码无法直接在Deno中运行,但可以通过理解Java示例来类比Deno中的实现)。
81 1