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

相关文章

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3