这个协议类采用了zigzag编码,这种编码是基于Variable-lengthquantity编码提出来的,因为Variable-lengthquantity编码对于负数的编码都需要很长的字节数,而zigzag编码对于绝对值小的数字,无论正负都可以采用较少的字节来表示,充分利用了Varint技术。所以这个协议类采用zigzag编码可以节省传输空间,使数据的传输效率更高。至于zigzag具体的编码实现方式可以网上查查,其实就是把从低位到最后一个还存在1(二进制)的最高位表示出来就可以了。这个协议类对外提供的方法和上面介绍的二进制协议相同,这样可以很方便使用者从一种协议改变到另一种协议。下面我同样结合scribe提供的Log方法来分析这个协议类的功能,不过不会像上面二进制协议在把整个过程分析了,我只会分析与协议相关的部分了,分析一些比较难懂的一些函数功能,分析的思路还是按照函数调用过程来分析。首先还是分析writeMessageBegin函数,下面是这个函数的实现代码:1templateclassTransport_uint32_tTCompactProtocolTTransport_::writeMessageBegin(23conststd::string&name,constTMessageTypemessageType,constint32_tseqid){45uint32_twsize=0;67wsize+=writeByte(PROTOCOL_ID);//写入这个协议的产品ID号:为0x8289wsize+=writeByte((VERSION_N&VERSION_MASK)|(((int32_t)messageTypeTYPE_SHIFT_AMOUNT)&TYPE_MASK));//写入此协议的版本号和消息类型:前3位是消息类型,后面5位是协议版本号10=writeVarint32(seqid);//写入请求序列号1213wsize+=writeString(name);//写入消息名称(也就是函数调用名称)1415returnwsize;//返回写入的大小,多少字节1617}因为这些协议类都是模板类,所以每一个函数也就是模板函数了。函数具体的功能代码里有详细注释,其中的writeByte函数就是写入一个字节到服务器。这里与二进制协议不同的是这里写入请求序列号(也就是对于所有的整型数)都调用的writeVarint32函数,这个函数就是采用zigzag编码写入整型数到服务器,代码如下:1templateclassTransport_uint32_tTCompactProtocolTTransport_::writeVarint32(uint32_tn){23uint8_tbuf[5];//对于一个整数,zigzag编码最大采用5个字节保存45uint32_twsize=0;67while(true){89if((n&~0x7F)==0){//判断除了最低7位是否还有其他高位为1(二进制)1011buf[wsize++]=(int8_t)n;//没有了代表着就是最后一个字节1213break;//退出循环1415}else{1617buf[wsize++]=(int8_t)((n&0x7F)|0x80);//取最低7位加上第8位(为1代表后续还有字节属于这个整数,为0代表这是这个整数的最后一个字节了。1819n=7;//移走已经编码的位数2021}2223}2425trans_-write(buf,wsize);//写入编码的字节数26返回写入的字节数2829}这个函数的功能就是对整数进行Variable-lengthquantity编码后写入,如果为负数需要处理。如果不处理那么每一个负数都需要5个字节来编码,因为最高位表示符号位,而负数的符号位用1表示(也就是说负数的最高位永远为1)。处理的方式也很简单(就是zigzag编码),就是把最高位(符号位)移动到最低位,最低位到次高位一次向高位移动一位,代码如下(就一句就实现了):1templateclassTransport_23uint32_tTCompactProtocolTTransport_::i32ToZigzag(constint32_tn){45return(n1)^(n31);67}上面写入整数和处理负数都是针对的32位的,当然也有64位的相应函数,实现方式相同。我们在回到writeMessageBegin函数,里面还有一个writeString函数用来写入一个字符串的,与二进制不同的是写入字符串长度也是采用了可变长度编码的方式写入,然后写入字符串的具体数据,它是调用另一个函数writeBinary写入,writeBinary实现代码如下:1templateclassTransport_23uint32_tTCompactProtocolTTransport_::writeBinary(conststd::string&str){45uint32_tssize=str.size();67uint32_twsize=writeVarint32(ssize)+ssize;//写入字符串的长度并计算写入的长度(包括字符串的长度)89trans_-write((uint8_t*)str.data(),ssize);//写入字符串的数据1011returnwsize;1213}写消息函数分析完毕以后我们在来看看对应的读消息函数readMessageBegin,看这个函数必须和写入消息的函数对应起来看,不然就不能理解它读取和处理的流程代码,具体实现如下代码:1templateclassTransport_uint32_tTCompactProtocolTTransport_::readMessageBegin(23std::string&name,TMessageType&messageType,int32_t&seqid){45uint32_trsize=0;67int8_tprotocolId;89int8_tversionAndType;1011int8_tversion;1213rsize+=readByte(protocolId);//读取协议产品ID号1415if(protocolId!=PROTOCOL_ID){//判断是不是这个协议的产品ID号,不是就抛出异常1617throwTProtocolException(TProtocolException::BAD_VERSION,Badprotocolidentifier);1819}2021rsize+=readByte(versionAndType);//读取此协议的版本号和消息类型2223version=(int8_t)(versionAndType&VERSION_MASK);//取出协议版本号2425if(version!=VERSION_N){//判断是不是对应的协议版本号,不是抛出异常2627throwTProtocolException(TProtocolException::BAD_VERSION,Badprotocolversion);2829}3031messageType=(TMessageType)((versionAndTypeTYPE_SHIFT_AMOUNT)&0x03);//取出消息类型3233rsize+=readVarint32(seqid);//读取请求序列号3435rsize+=readString(name);//读取消息名称(函数名称)3637returnrsize;//返回读取的长度(字节)3839}通过对照写入消息的函数就很容易理解,因为你写入什么我就读什么并且判断是不是相同协议写入的,具体分析可以看上面的代码和详细的注释。而且还有一点就是具体的写入数据类型的函数也是采用对应类型的读函数,例如读可变长整型写入就是采用可变长读函数readVarint32,写字符串对应读字符串函数readString,对照相应的写入函数来看这些读数据函数就非常好理解了,就不具体分析这些读函数了。下面在分析几个复合数据类型的写入函数,因为这些写入函数存在一定技巧不容易(或者说不那么直观吧)理解清楚。首先看看struct类型的数据写入的过程,它分为写入开始、中间处理和写入结束。下面是开始写入struct的代码:1templateclassTransport_23uint32_tTCompactProtocolTTransport_::writeStructBegin(constchar*name){45(void)name;67lastField_.push(lastFieldId_);//把最后写入的字段ID压入堆栈89lastFieldId_=0;//重新设置为01011return0;1213}这开始写入的函数没有做什么具体的工作,只是把最后写入的字段ID压入堆栈,这样做的目的是处理那种struct嵌套的数据结构类型。Struct里面的是一个一个的字段,所以根据struct的字段个数分别调用字段写入函数依次写入,字段写入函数定义如下:1templateclassTransport_int32_tTCompactProtocolTTransport_::writeFieldBeginInternal(23constchar*name,constTTypefieldType,constint16_tfieldId,int8_ttypeOverride){45(void)name;//为了防止编译器产生警告信息67uint32_twsize=0;89//如果存在对于对应的类型就转换为对应的1011int8_ttypeToWrite=(typeOverride==-1?getCompactType(fieldType):typeOverride);1213//检查字段ID是否使用了增量编码1415if(fieldIdlastFieldId_&&fieldId-lastFieldId_=15){//如果使用了增量编码并增量且小于等于151617wsize+=writeByte((fieldId-lastFieldId_)4|typeToWrite);//字段ID和数据类型一起写入1819}else{//否则单独写入2021wsize+=writeByte(typeToWrite);//写入数据类型2223wsize+=writeI16(fieldId);//写入字段ID2425}2627lastFieldId_=fieldId;//保存写入字段ID为最后一个写入的ID2829returnwsize;//返回写入的长度3031}当结构体里面的每一个字段都写入以后还需要调用writeStructEnd函数来处理结束一个struct的写入,主要处理是字段ID的相关内容,实现代码如下:1templateclassTransport_uint32_tTCompactProtocolTTransport_::writeStructEnd(){23lastFieldId_=lastField_.top();//取得最后一次压入堆栈的字段ID号45lastField_.pop();//弹出以取得的字段ID67return0;89}同样的结构体也有对应的读取函数,具体实现就不在具体分析了!下面继续分析一些特殊的处理代码,首先看看负数在进行zigzag编码前怎样处理,对于32位和64位都是一句代码就搞定,如下代码:1return(n1)^-(n&1);这句代码的作用就是把最高位的符号位移动到最低位,然后最低位到次高位依次向高位移动一位,这样就避免了所有负数