我的新浪微博:http://weibo.com/freshairbrucewoo。
欢迎大家相互交流,共同提高技术。
JSON(JavaScriptObjectNotation)是一种数据交换格式,是以JavaScript为基础的数据表示语言,是在以下两种数据结构的基础上来定义基本的数据描述格式的:1)含有名称/值对的集合;2)一个有序的列表。对于JSON,其部分数据结构的BNF定义如下所示。形如{“name”:”ldxian”,”age”:23}就表示一个JSON对象,其有两个属性,值分别为ldxian和23。其余的如数字、注释等跟其他编程语言差不多。下面就开始看看facebook的thrift是怎样实现json协议的。
首先看看Thrift的数据类型和JSon数据类型的对应关系:
(1)Thrift的所有整数类型JSon都作为数字表示;
(2)Thrift的double类型也作为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)Thrift的lists和sets被表示为JSon的array(数组),其中数组的第一个元素表示Thrift元素的数据类型,数组第二值表示后面Thrift元素的个数。接着后面就是所有的元素。
(7)Thrift的maps被表示为JSon的array,其中前两个值分别表示键和值的类型,跟着就是键值对的个数,接着就是包含具体键值对的JSon对象了。注意了Json的键只能是字符串,这就是要求Thrift的maps类型的键必须是数字和字符串的,并且数字需要序列化为字符串。
(8)Thrift的messages(消息)被表示为JSon的array,前四个元素分别代表协议版本、消息名称、消息类型和序列ID。
知道例如了每一个数据类型的对应关系了,并且知道特殊情况下怎样转换对应的数据,那么在实现对Thrift的传输数据进行Json编码就是很容易的事情了,就是按照对应的关系先进行Json编码然后才写入传输层由他发送到网络中的另一端,而另一端接收到数据的时候就按照Json协议解码就可以了,整个过程大概就是这样了。下面分析实现Json编码协议过程中一些比较重要的代码。
Json编码协议的实现和前面介绍的协议都一样需要实现同样的接口给上层调用。在真正实现这些函数以前,它做了很多的准备工作,主要定义一些静态数据和帮助函数,静态数据主要是一些特殊字符或符号的代表常量和一些转义字符的表示等,帮助函数主要就是数据类型名称和id的相互转换、根据类型的短的表示字符串得到具体的数据类型等。这些类容都很直观且简单,一看代码就懂了,就不具体分析介绍了。下面主要介绍几个重点函数的实现过程。还是按照前面协议的惯例,第一个分析开始写消息的函数writeMessageBegin,代码如下:
uint32_tresult=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_tresult=context_->write(*trans_);//默认什么都不做
std::stringval(boost::lexical_cast<std::string>(num));//调用boost库的类型转换函数把数字转换为字符串
boolescapeNum=context_->escapeNum();//是否是转义字符,默认false
if(escapeNum){//不会执行,如果是转义字符就用json的字符串分隔符分隔开
trans_->write(&kJSONStringDelimiter,1);
result+=1;
}
trans_->write((constuint8_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_iteratoriter(str.begin());//字符串遍历用的迭代器
std::string::const_iteratorend(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_toutCh=kJSONCharTable[ch];//ascii编码在前0x30的用一个表格来对应怎样处理
if(outCh==1){//表格中的值是1就直接写入
trans_->write(&ch,1);
}
elseif(outCh>1){//表格中的值大于1就是需要转义字符
trans_->write(&kJSONBackslash,1);//写入转义字符/
trans_->write(&outCh,1);//写入具体的字符
}
else{//为0的就先写入转义序列,在转换为16进制写入
returnwriteJSONEscapeChar(ch);//先写入\00在写入两位16进制xx
}
}
这段代码主要判断字符的ascii编码是否大于48(0x30),如果大于等于的话除了转义字符(\)需要转义字符以外(\\)都直接写入,小于的话就查询一个表中的值来决定这样处理,处理方式请看代码注释。
写入一个消息数据大致过程和涉及到的json编码写入都已经介绍完了,下面就开始看看对于的读取一个消息数据的过程和涉及到json解码的一些内容,先看读消息函数readMessageBegin,代码如下:
uint32_tresult=readJSONArrayStart();//读取数字的开始符号[
uint64_ttmpVal=0;
result+=readJSONInteger(tmpVal);//读取协议版本
if(tmpVal!=kThriftVersion1){//版本不对就抛出异常
throwTProtocolException(TProtocolException::BAD_VERSION,"Messagecontainedbadversion.");
}
result+=readJSONString(name);//读取消息名称
result+=readJSONInteger(tmpVal);//读取消息类型
messageType=(TMessageType)tmpVal;
result+=readJSONInteger(tmpVal);//读取序列号
seqid=tmpVal;
可以看出消息写入函数先写入什么读取函数就对于先读取什么。这里涉及的读取json格式的数据也包括整数和字符串,那么先看看怎么解码的json数字,函数readJSONInteger主要代码如下:
std::stringstr;
result+=readJSONNumericChars(str);//读取json数字字符串
try{
num=boost::lexical_cast<NumberType>(str);//字符串转换为数字类型数据
}
catch(boost::bad_lexical_caste){//可能抛出异常,处理异常
thrownewTProtocolException(TProtocolException::INVALID_DATA,
"Expectednumericvalue;got\""+str+"\"");
}
主要代码比较少,就是调用另一个函数先把代表数字的字符串一个一个的读取出来(读取的时候会判断是不是json有效的数字字符),然后通过boost的库函数转换为具体的一种数字类型(如int、double),转换可能抛出异常(无效的数据)。继续看看字符串的解码函数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_tpos=kEscapeChars.find(ch);//查找是不是转义字符之一(\"\\bfnrt)
if(pos==std::string::npos){//不是就抛出无效数据异常
throwTProtocolException(TProtocolException::INVALID_DATA,"Expectedcontrolchar,got'"+
std::string((constchar*)&ch,1)+"'.");
}
ch=kEscapeCharVals[pos];//根据拿到这个转义字符
}
}
str+=ch;//处理后的字符加入到解码后的字符串中,也就是最终解码结果的字符串
}
字符串的解码也是一个字符一个字符的,和写入一样,每一个字符都有可能是特殊字符(转义字符或是需要被转义的字符),如果是特殊字符就需要处理后才能加入解码后的结果字符串中。
总结:上面对于json的分析只针对了消息数据结构的编码写入和对于的读取解码,是一个完整的json协议通信了,当然还有其他的数据结构(如struct、map、set、list和double等),它们也有自己需要处理的特殊地方,但是总体的流程都是一样的,而且难度不大,只要按照既定好的协议json编码写入和json解码读取就行了。上面的分析提供个完整分析的思路,想完完全全了解整个thrift采用的json协议还是读取源代码(TJSONProtocol.h和TJSONProtocol.cpp文件)。其中源代码里面还有一点知识就是thrift采用自己实现的base64编码和解码,需
要自己实现的可以借鉴其实现。Json协议类分析到此结束!
下一个协议分析更NB:稠密协议类TDenseProtocol。请听下回分解!~
分享到:
相关推荐
thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码thrift源码...
使用wireshark抓取thrift协议接口调用
thrift-enhancer是一组支持thrift协议的加强包,设计...thrift-translator: 提供动态解析idl并生成参数对象的能力,动态生成的参数对象可以自动转换为thrift协议数据,同时提供 thrift与json、xml的双向转换, 动态解析
thrift0.9.1版本的jar包,还有源码,可供大家参考学习使用。
Apache Thrift Java实战源码,包含了客户端和服务端源码,客户端和服务端是分开的,如果需要放到一个工程,直接把Client.java文件复制到服务端运行即可。
thrift源码+DEMO+简单教程
windows下vs2010完美编译thrift,包含lib文件和src源码,thrift版本为thrift-0.11.0
不要认为thrift能在*.thrift文件中定义调度之类的工作 thrift通过IDL(接口描述语言)来生成各种语言需要的接口代码。 执行thrift文件命令: java:thrift-0.9.1.exe --gen java test.thrift java:thrift-0.9.1.exe ...
thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, ...
libthrift-1.0.0.jar,thrift例子,thrift源码 里面有你想要java版的thrift全部文件
详细介绍了Apache Thrift在Ubuntu以及Windows下基于C++和Java语言的安装和运行。附有小例子,亲自测试通过。所述方法网上应该有教程,但大多零散不统一或者不完整,因此本人整理了一份,特来分享。
thrift实现http协议案例,thrift官方好像就一个socket,此案例是通过继承servlet实现http协议通信
thrift官方代码+与dubbo集成支持原生thrift协议
使用thrift js时,PRotocol需要使用TJSONPRotocol/TBinaryProtocol协议,Thrift.Protocol对应的是TJSONPRotocol。Transport需要使用TXHRTransport/TWebSocketTransport通道,Thrift.Transport对应的是TXHRTransport...
thrift,Apache Thrift 0.9.2 版本,解压后直接直接安装,可伸缩的跨语言服务开发框架,命令: 解压命令:tar -zxf thrift-0.9.2.tar.gz 安装命令:./configure --with-lua=no && make && make install 查看版本:...
spark-hive_2.11-2.3.0...spark-hive-thriftserver_2.11-2.3.0.jar log4j-2.15.0.jar slf4j-api-1.7.7.jar slf4j-log4j12-1.7.25.jar curator-client-2.4.0.jar curator-framework-2.4.0.jar curator-recipes-2.4.0.jar
NULL 博文链接:https://andilyliao.iteye.com/blog/1931911
thrift是一种可伸缩的跨语言服务的发展软件框架。它结合了功能强大的软件堆栈的代码生成引擎,以建设服务,工作效率和无缝地与C + +,C#,Java,Python和PHP和Ruby结合。thrift是facebook开发的,我们现在把它作为...
flume通过thrift协议收集日志-Python。博客地址:http://blog.csdn.net/wangshuwei5/article/details/48902757
本设计源码提供了一个基于Scala的Spark Thrift Server。项目包含12731个文件,主要使用Scala、Java、Python、Shell、JavaScript、CSS、HTML、Ruby和C编程语言。文件类型包括3539个Scala源代码文件、1559个Q文件、...