简介

程序和程序之间的数据传输方式有很多,可以通过二进制协议来传输,比较流行的像是thrift协议或者google的protobuf。这些二进制协议可以实现数据的有效传输,并且通过二进制的形式可以节省数据的体积,在某些速度和效率优先的情况下是非常有效的。并且如果不同的编程语言之间的相互调用,也可以通过这种二进制的协议来实现。

虽然二进制更加快速和有效,但是对于程序员来说不是很友好,因为一个人很难直接读取二进制文件,虽然也存在一些一些文本的数据传输方式,比如XML,但是XML的繁琐的标签导致了XML在使用中有诸多的不便。于是一种通用的文本文件传输格式json诞生了。

能读到这篇文章的朋友肯定对json不陌生了,当然还有一些更加简洁的文件格式,比如YAML,感兴趣的朋友可以更深入的了解一下。

这里我们想要讲的是netty对json的解码。

java中对json的支持

在java中我们json的使用通常是将一个对象转换成为json进行数据传输,或者将接收到json进行解析,将其转换成为对象。

可惜的是在JDK中并没有提供给一个好用的JSON工具,所以我们一般需要借助第三方的JSON包来实现Object和JSON之间的转换工作。

通常使用的有google的GSON,阿里的FastJSON和jackson等。

这里我们使用google的GSON来进行介绍。

这里我们主要讲解的是java中对象和json的互相转换,所以GSON中其他更加强大的功能这里就不介绍了。

首先我们创建一个JAVA对象,我们定义一个Student类,如下所示:

    static class Student {
String name;
String phone;
Integer age; public Student(String name, String phone, Integer age) {
this.name = name;
this.phone = phone;
this.age = age;
}
}

这个类中,我们为Student定义了几个不同的属性和一个构造函数。接下来我们看下如何使用GSON来对这个对象进行JSON的转换:

        Student obj = new Student("tina","188888888",18);
Gson gson = new Gson();
String json = gson.toJson(obj);
System.out.println(json);
Student obj2 = gson.fromJson(json, Student.class);
System.out.println(obj2);

GSON使用起来非常简单,我们构建好Gson对象之后,直接调用它的toJson方法即可将对象转换成为json字符串。

然后调用json的fromJson方法就可以将json字符串转换成为对象。

上面的代码输出如下:

{"name":"tina","phone":"188888888","age":18}
com.flydean.JsonTest$Student@4534b60d

netty对json的解码

netty为json提供了一个解码器叫做JsonObjectDecoder,先来看下JsonObjectDecoder的定义:

public class JsonObjectDecoder extends ByteToMessageDecoder

和前面讲解的base64,byte数组不同的是,JsonObjectDecoder继承的是ByteToMessageDecoder而不是MessageToMessageDecoder。

这说明JsonObjectDecoder是直接从ByteBuf转换成为Json Object对象。

我们知道JDK中并没有JSON这个对象,所有的对象都是从第三方包中引入的,netty并没有引入新的对象,所以netty中从Json中解析出来的对象还是一个ByteBuf对象,在这个ByteBuf中包含了一个Json对象。

JsonObjectDecoder的解析逻辑是怎么样的呢?

首先来看下JsonObjectDecoder中定义的4个state:

    private static final int ST_CORRUPTED = -1;
private static final int ST_INIT = 0;
private static final int ST_DECODING_NORMAL = 1;
private static final int ST_DECODING_ARRAY_STREAM = 2;

ST_INIT表示的是decode的初始状态,ST_CORRUPTED表示的是decode中出现的异常状态。

ST_DECODING_NORMAL代表的是一个普通的json,如下所示:

{
"source": "web",
"type": "product_info",
"time": 1641967014440,
"data": {
"id": 30000084318055,
"staging": false
},
"dataId": "123456"
}

ST_DECODING_ARRAY_STREAM代表的是一个数组,对于数组来说,数组也是一个对象,所以数组也可以用json表示,下面就是一个常见的json数组:

[ "Google", "Runoob", "Taobao" ]

JsonObjectDecoder的解码逻辑比较简单,它主要是读取ByteBuf中的数据,通过判断读取的数据和json中特有的大括号,中括号,逗号等分隔符来分割和解析json对象。

要注意的是,JsonObjectDecoder要解码的ByteBuf中的消息应该是UTF-8编码格式的,为什么需要UTF-8格式呢?

这是因为json中那些特有的分隔符,即使在UTF-8中也是用一个byte来存储的,这样我们在读取数据的过程中,可以通过读取的byte值和json的分隔符进行比较,从而来确定json中不同对象的界限。

如果换成其他的编码方式,json中的分隔符可能会用多个byte来表示,这样对我们的解析就提高了难度,因为我们需要知道什么时候是分隔符的开始,什么时候是分隔符的结束。

它的核心解码逻辑如下,首先从ByteBuf中读取一个byte:

byte c = in.getByte(idx);

然后通过调用decodeByte(c, in, idx); 来判断当前的位置是开括号,还是闭括号,是在一个对象的字符串中,还是一个新的对象字符串。

首先需要对当前的state做一个判断,state判断调用的是initDecoding方法:

    private void initDecoding(byte openingBrace) {
openBraces = 1;
if (openingBrace == '[' && streamArrayElements) {
state = ST_DECODING_ARRAY_STREAM;
} else {
state = ST_DECODING_NORMAL;
}
}

接着就是对当前的state和自定义的4个状态进行比较,如果是普通的json对象,并且对象已经是闭括号状态,说明该对象已经读取完成,可以将其进行转换并输出了:

 if (state == ST_DECODING_NORMAL) {
decodeByte(c, in, idx);
if (openBraces == 0) {
ByteBuf json = extractObject(ctx, in, in.readerIndex(), idx + 1 - in.readerIndex());
if (json != null) {
out.add(json);
}
...

如果state表示目前是一个数组对象,数组对象中可能包含多个对象,这些对象是通过逗号来区分的。逗号之间还可能会有空格,所以需要对这些数据进行特殊判断和处理,如下所示:

else if (state == ST_DECODING_ARRAY_STREAM) {
decodeByte(c, in, idx); if (!insideString && (openBraces == 1 && c == ',' || openBraces == 0 && c == ']')) {
for (int i = in.readerIndex(); Character.isWhitespace(in.getByte(i)); i++) {
in.skipBytes(1);
}
int idxNoSpaces = idx - 1;
while (idxNoSpaces >= in.readerIndex() && Character.isWhitespace(in.getByte(idxNoSpaces))) {
idxNoSpaces--;
}
ByteBuf json = extractObject(ctx, in, in.readerIndex(), idxNoSpaces + 1 - in.readerIndex());
if (json != null) {
out.add(json);
}
....

最后将解析出来的json对象放入byteBuf的out list中,整个解析到此结束。

总结

以上就是netty中json核心解码器JsonObjectDecoder的使用,它的本质是通过判断json对象中的分割符来分割多个json字符串,然后将分割后的json字符串存入ByteBuf中输出。

看到这里,大家可能会疑惑了,decoder不是和encoder一起出现的吗?为什么netty中只有JsonObjectDecoder,而没有JsonObjectEncoder呢?

事实上,这里的Json对象就是一个包含Json字符的字符串,这个字符串被写入到ByteBuf中,所以这里并不需要特殊的encoder。

本文已收录于 http://www.flydean.com/14-3-netty-codec-json/

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

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

netty系列之:netty中的核心解码器json的更多相关文章

  1. 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?

    [读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...

  2. Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化

    Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...

  3. netty系列之:netty中的核心编码器base64

    目录 简介 netty codec的实现逻辑 netty中Base64的实现 netty中的base64编码和解码器 Base64Encoder Base64Decoder 总结 简介 我们知道数据在 ...

  4. netty系列之:netty中的核心编码器bytes数组

    目录 简介 byte是什么 netty中的byte数组的工具类 netty中byte的编码器 总结 简介 我们知道netty中数据传输的核心是ByteBuf,ByteBuf提供了多种数据读写的方法,包 ...

  5. netty系列之:netty实现http2中的流控制

    目录 简介 http2中的流控制 netty对http2流控制的封装 Http2FlowController Http2LocalFlowController Http2RemoteFlowContr ...

  6. netty系列之:netty中各不同种类的channel详解

    目录 简介 ServerChannel和它的类型 Epoll和Kqueue AbstractServerChannel ServerSocketChannel ServerDomainSocketCh ...

  7. netty系列之:netty架构概述

    目录 简介 netty架构图 丰富的Buffer数据机构 零拷贝 统一的API 事件驱动 其他优秀的特性 总结 简介 Netty为什么这么优秀,它在JDK本身的NIO基础上又做了什么改进呢?它的架构和 ...

  8. Netty系列之Netty可靠性分析

      作者 李林锋 发布于 2014年6月19日 | 29 讨论 分享到:微博微信FacebookTwitter有道云笔记邮件分享 稍后阅读 我的阅读清单   1. 背景 1.1. 宕机的代价 1.1. ...

  9. 【转】Netty系列之Netty编解码框架分析

    http://www.infoq.com/cn/articles/netty-codec-framework-analyse/ 1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称 ...

随机推荐

  1. 面试之Java String 编码相关

    实话说,作为一个多年Java老年程序员,直到近来,在没有决心花时间搞清楚Java String的编码相关问题之前, 自己也都还是似懂非懂,一脸懵逼的.设想如果在面试中,有同学能够条理清晰的回答下面的问 ...

  2. Future和FutureTask的区别

    java中有Future和FutureTask这两个类 Future是一个接口,代表可以取消的任务,并可以获得任务的执行结果 FutureTask 是基本的实现了Future和runnable接口  ...

  3. 同一套代码部署多个实例来并行完成mysql某项任务,且避免重复执行

    我经常会碰到一些耗时较长的任务,譬如更新5千万条表数据中的某个字段,代码中可以通过分页依次读取db,然后更新即可.但是耗时极长,那么能否通过将代码部署多个实例,譬如启动多个docker来并行执行任务, ...

  4. 什么是SpringCloudConfig?

    在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件.在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持 ...

  5. 树莓派基本配置与Docker的安装

    一.树莓派的安装 下载镜像 在 树莓派官网 https://www.raspberrypi.org/downloads/raspbian/ Win32 DiskImager,是一个把系统镜像写入SD卡 ...

  6. Linux分区问题

    一.基本分区的作用及其大小 /boot分区: 存放引导文件和Linux内核文件等. 引导文件:判断启动哪一个操作系统或启动哪个内核. 内核:管理硬件和软件资源,程序与硬件之间的桥梁. 分区大小:100 ...

  7. 如何解决Visual Studio 2017 运行后控制台窗口一闪就消失了

    出现这种情况的原因 安装使用Visual Studio 2017 后,用Ctrl+F5运行程序,结果控制台窗口一闪就没了,也没有出现"press any key to continue-&q ...

  8. 顺利通过EMC实验(19)

  9. 微信小程序实时通讯(websocket)问题

    这几天值班忙的不要不要,人工智能这块看的都是零零散散,今天就来写写小程序的实时通讯吧.小程序端://这个是连接 lianjie:function(){ var socketOpen = false / ...

  10. 手写封装防抖debounce

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...