写点什么

netty 系列之:netty 中的核心解码器 json

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

    阅读完需:约 11 分钟

netty系列之:netty中的核心解码器json

简介

程序和程序之间的数据传输方式有很多,可以通过二进制协议来传输,比较流行的像是 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/

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

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

发布于: 刚刚阅读数: 3
用户头像

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

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

评论

发布
暂无评论
netty系列之:netty中的核心解码器json_Java_程序那些事_InfoQ写作平台