Thrift之TProtocol类体系原理及源码详细解析之JSon协议类TJSONProtocol

本文主要是介绍Thrift之TProtocol类体系原理及源码详细解析之JSon协议类TJSONProtocol,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

http://blog.csdn.net/wanweiaiaqiang/article/details/7657915




JSON (JavaScript Object Notation)是一种数据交换格式,是以JavaScript为基础的数据表示语言,是在以下两种数据结构的基础上来定义基本的数据描述格式的:1) 含有名称/值对的集合;2) 一个有序的列表。对于 JSON,其部分数据结构的BNF 定义如下所示。形如{“name”:”ldxian”,”age”:23}就表示一个JSON 对象,其有两个属性,值分别为ldxian和23。其余的如数字、注释等跟其他编程语言差不多。下面就开始看看facebookthrift是怎样实现json协议的。

首先看看Thrift的数据类型和JSon数据类型的对应关系:

(1) Thrift的所有整数类型JSon都作为数字表示;

(2) Thriftdouble类型也作为JSon的数字表示,特殊的值用字符串表示,如下:

a. NaN表示不是数字值;

b. Infinity表示正无穷大;

c. –Infinity表示负无穷大。

(3) Thrift的字符串表示为JSon的字符串并加上一些转义字符;

(4) Thrift的二进制值并编码为base64编码然后作为JSon的字符串;

(5) Thrift的结构体表示为JSon的对象,字段ID作为key,字段值用一个单一的键值对的JSon对象表示。键是一个简短的字符串代表特定的类型,接着就是值。有效的类型标识是:"tf"代表 bool,"i8" 表示 byte,"i16"表示16位整数,"i32"表示32位整数,"i64"表示64位整数,"dbl"表示双精度浮点型,"str" 表示字符串(包括二进制),"rec"表示结构体 ("records"),"map"表示 map,"lst" 表示 list, "set" 表示set。

(6) Thriftlistssets被表示为JSonarray(数组),其中数组的第一个元素表示Thrift元素的数据类型,数组第二值表示后面Thrift元素的个数。接着后面就是所有的元素。

(7) Thriftmaps被表示为JSonarray,其中前两个值分别表示键和值的类型,跟着就是键值对的个数,接着就是包含具体键值对的JSon对象了。注意了Json的键只能是字符串,这就是要求Thriftmaps类型的键必须是数字和字符串的,并且数字需要序列化为字符串。

(8) Thriftmessages(消息)被表示为JSonarray,前四个元素分别代表协议版本、消息名称、消息类型和序列ID

知道例如了每一个数据类型的对应关系了,并且知道特殊情况下怎样转换对应的数据,那么在实现对Thrift的传输数据进行Json编码就是很容易的事情了,就是按照对应的关系先进行Json编码然后才写入传输层由他发送到网络中的另一端,而另一端接收到数据的时候就按照Json协议解码就可以了,整个过程大概就是这样了。下面分析实现Json编码协议过程中一些比较重要的代码。

Json编码协议的实现和前面介绍的协议都一样需要实现同样的接口给上层调用。在真正实现这些函数以前,它做了很多的准备工作,主要定义一些静态数据和帮助函数,静态数据主要是一些特殊字符或符号的代表常量和一些转义字符的表示等,帮助函数主要就是数据类型名称和id的相互转换、根据类型的短的表示字符串得到具体的数据类型等。这些类容都很直观且简单,一看代码就懂了,就不具体分析介绍了。下面主要介绍几个重点函数的实现过程。还是按照前面协议的惯例,第一个分析开始写消息的函数writeMessageBegin,代码如下:

  uint32_t result = writeJSONArrayStart();//json数组开始

  result += writeJSONInteger(kThriftVersion1);//把协议版本号转换为json格式后写入

  result += writeJSONString(name);//把消息名称转化为json字符串后写入

  result += writeJSONInteger(messageType);//同理:消息类型

  result += writeJSONInteger(seqid);//序列号

这里贴出来的代码就不在是完成的函数定义了,只是贴出一些主要代码,以后也会按照这种方式了,前面的主要为了完整性的考虑,后面的分析可能更多需要结合源代码一起了。现在的目的就是提供思路和分析程序设计的思路、思想,想要了解完整的实现还必须的去看完整的源代码。

通过前面介绍的Thrift的数据类型和Json数据类型的对于关系可知,发送一个messages的数据结构对于json来说是转换为一个数组,所以writeMessageBegin函数的实现首先调用函数writeJSONArrayStart写入一个数组开始的表示符号‘[’,同样在介绍的时候会调用相应的函数写入数组结束符号,后面就不单独介绍了。然后按照messages转化为json的格式一次写入了协议版本、消息名称、消息类型和序列号。其中消息名称是字符串数据类型,所以转化为json的字符串发送;而协议版本、消息类型和序列号都是整数所以转化为json的数字传输。其实这个实现过程和前面介绍的协议都差不多,不同的就是内容采用了json格式来发送,所以下面主要看看怎样把这些数据类型转化为json,先看看writeJSONInteger函数,它是把整数转化为数字,主要代码如下:

  uint32_t result = context_->write(*trans_);//默认什么都不做

  std::string val(boost::lexical_cast<std::string>(num));//调用boost库的类型转换函数把数字转换为字符串

  bool escapeNum = context_->escapeNum();//是否是转义字符,默认false

  if (escapeNum) {//不会执行,如果是转义字符就用json的字符串分隔符分隔开

    trans_->write(&kJSONStringDelimiter, 1);

    result += 1;

  }

  trans_->write((const uint8_t *)val.c_str(), val.length());//写入转换后的字符串到传输层

  result += val.length();//写入长度计算

  if (escapeNum) {

    trans_->write(&kJSONStringDelimiter, 1);

    result += 1;

  }

其实就是把整数转换成字符串了,然后写入传输层。继续看看字符串的转换写入函数writeJSONString,如下:

  result += 2; // 这长度是两个双引号的,字符串的分隔符

  trans_->write(&kJSONStringDelimiter, 1);//写入字符串开始的双引号

  std::string::const_iterator iter(str.begin());//字符串遍历用的迭代器

  std::string::const_iterator end(str.end());//结束处

  while (iter != end) {

    result += writeJSONChar(*iter++);//一个字符一个字符的写入json字符

  }

  trans_->write(&kJSONStringDelimiter, 1);//字符串介绍的双引号

由上面代码可以看出写入字符串是一个一个字符的写入的,为什么需要这样呢?因为字符串可能包含特殊的字符,例如转义字符,所以对于每一个写入的字符都需要判断,如果是特殊字符就需要特殊处理(转义字符处理)。继续看看字符写入writeJSONChar,这个函数具体的体现了特殊字符的处理,如下:

if (ch >= 0x30) {//字符的整数值如果大于0x30

    if (ch == kJSONBackslash) { // ascii编码大于等于0x30的特殊字符只有'\'

      trans_->write(&kJSONBackslash, 1);//转义字符

      trans_->write(&kJSONBackslash, 1);//写入'\'

    }

    else {

      trans_->write(&ch, 1);//其余直接写入

    }

  }

  else {

    uint8_t outCh = kJSONCharTable[ch];//ascii编码在前0x30的用一个表格来对应怎样处理

    if (outCh == 1) {//表格中的值是1就直接写入

      trans_->write(&ch, 1);

    }

    else if (outCh > 1) {//表格中的值大于1就是需要转义字符

      trans_->write(&kJSONBackslash, 1);//写入转义字符/

      trans_->write(&outCh, 1);//写入具体的字符

    }

    else {//0的就先写入转义序列,在转换为16进制写入

      return writeJSONEscapeChar(ch);//先写入\00在写入两位16进制xx

    }

  }

这段代码主要判断字符的ascii编码是否大于480x30),如果大于等于的话除了转义字符(\)需要转义字符以外(\\)都直接写入,小于的话就查询一个表中的值来决定这样处理,处理方式请看代码注释。

写入一个消息数据大致过程和涉及到的json编码写入都已经介绍完了,下面就开始看看对于的读取一个消息数据的过程和涉及到json解码的一些内容,先看读消息函数readMessageBegin,代码如下:

  uint32_t result = readJSONArrayStart();//读取数字的开始符号[

  uint64_t tmpVal = 0;

  result += readJSONInteger(tmpVal);//读取协议版本

     if (tmpVal != kThriftVersion1) {//版本不对就抛出异常

    throw TProtocolException(TProtocolException::BAD_VERSION, "Message contained bad version.");

  }

  result += readJSONString(name);//读取消息名称

  result += readJSONInteger(tmpVal);//读取消息类型

  messageType = (TMessageType)tmpVal;

  result += readJSONInteger(tmpVal);//读取序列号

  seqid = tmpVal;

可以看出消息写入函数先写入什么读取函数就对于先读取什么。这里涉及的读取json格式的数据也包括整数和字符串,那么先看看怎么解码的json数字,函数readJSONInteger主要代码如下:

  std::string str;

  result += readJSONNumericChars(str);//读取json数字字符串

  try {

    num = boost::lexical_cast<NumberType>(str);//字符串转换为数字类型数据

  }

  catch (boost::bad_lexical_cast e) {//可能抛出异常,处理异常

    throw new TProtocolException(TProtocolException::INVALID_DATA,

                                 "Expected numeric value; got \"" + str + "\"");

  }

主要代码比较少,就是调用另一个函数先把代表数字的字符串一个一个的读取出来(读取的时候会判断是不是json有效的数字字符),然后通过boost的库函数转换为具体的一种数字类型(如intdouble),转换可能抛出异常(无效的数据)。继续看看字符串的解码函数readJSONString,主要代码如下:

  readJSONSyntaxChar(kJSONStringDelimiter);//读取一个字符并且判断是都是传递进去的"

  while (true) {

    ch = reader_.read();//读取一个字符

    if (ch == kJSONStringDelimiter) {//如果是字符串分隔符(",就结束了一一个字符串的读取

      break;

    }

    if (ch == kJSONBackslash) {//判断是不是反斜杠

      ch = reader_.read();//是就在读取下一个字符

      if (ch == kJSONEscapeChar) {//是不是转义字符序列开始符号(u

        result += readJSONEscapeChar(&ch);//是就读取转义序列开始符号(00xx

      }

      else {

        size_t pos = kEscapeChars.find(ch);//查找是不是转义字符之一(\"\\bfnrt

        if (pos == std::string::npos) {//不是就抛出无效数据异常

          throw TProtocolException(TProtocolException::INVALID_DATA, "Expected control char, got '" +

                                   std::string((const char *)&ch, 1)  + "'.");

        }

        ch = kEscapeCharVals[pos];//根据拿到这个转义字符

      }

    }

str += ch;//处理后的字符加入到解码后的字符串中,也就是最终解码结果的字符串

  }

字符串的解码也是一个字符一个字符的,和写入一样,每一个字符都有可能是特殊字符(转义字符或是需要被转义的字符),如果是特殊字符就需要处理后才能加入解码后的结果字符串中。

总结:上面对于json的分析只针对了消息数据结构的编码写入和对于的读取解码,是一个完整的json协议通信了,当然还有其他的数据结构(如structmapsetlistdouble等),它们也有自己需要处理的特殊地方,但是总体的流程都是一样的,而且难度不大,只要按照既定好的协议json编码写入和json解码读取就行了。上面的分析提供个完整分析的思路,想完完全全了解整个thrift采用的json协议还是读取源代码(TJSONProtocol.hTJSONProtocol.cpp文件)。其中源代码里面还有一点知识就是thrift采用自己实现的base64编码和解码,需


要自己实现的可以借鉴其实现。 Json 协议类分析到此结束!

下一个协议分析更NB:稠密协议类TDenseProtocol。请听下回分解!~




这篇关于Thrift之TProtocol类体系原理及源码详细解析之JSon协议类TJSONProtocol的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/810968

相关文章

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

Python中json文件和jsonl文件的区别小结

《Python中json文件和jsonl文件的区别小结》本文主要介绍了JSON和JSONL两种文件格式的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下... 众所周知,jsON 文件是使用php JSON(JavaScripythonpt Object No

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行