3.3 消息流化的基本元素
3.3.1 流化的基本元素
由第2章的内容我们知道,无论多么复杂的消息表示,其流化的实现,最终都要落实到一些基本数据类型的流化实现上来,而新消息体定义的流化专用代码(严格顺序化的宏定义)需要用到流化公共代码(StreamlizeTemplate.h)中对各种基本数据类型的流化接口定义。
由StreamlizeTemplate.h的定义可以看到,在应用系统程序员层面,如果要定义一个消息体,可以用到的基本数据类型如表3.1所示。
表3.1 消息体系中的基本数据类型
表3.1中最左边的xxx_即代表我们在第2章中流化专用与公共代码中的“MESSAGE_SEND_”、“MESSAGE_RECEIVE_”、“MESSAGE_ CLEAR_”等,分别表示该宏定义是用于发送、接收或清空该变量空间。
下面,我们对这些消息定义中的基本数据类型分别进行介绍。
3.3.2 数值型
数值类型的变量在C/C++语言中主要包括short,int,long,long long,long double,float,double这几种,在Java中除了long long,long double外,其他都有相同的对应,但差别是long型在Java中永远都是8个字节。
前面讲过,采用了XDR表示法后,short型可以丢弃,而为了解决32位与64位机器的平台无关问题,long型与int型也变成一样,然后再去掉并不常用的long double型,现在,我们只剩下了int(long),float,double,long long四种数值类型。
再来看看long long型与double型,它们都是8个字节,一个是长整型,一个是双浮点型,流化表示后,都是字节序规则相同的四个字节。因此,在消息表示中,还可以将long long型与double型统一起来(由于long long写在标准的消息定义代码中看上去不是那么规范)。
因此,在消息定义中只有int(long),float,double三种数值类型。
1.整型数
消息表示代码中的整型数是指消息各组成部分(消息头、消息类型块、消息体)的代码中以int、long关键字定义的基本数据类型变量。
回头看第2章消息表示法的代码,发现无论是各消息公用的公共代码,还是与具体消息功能类型一一对应的专有代码,其中的整型数,都是以4个字节的int型和long型定义。当然,有些读者可能还有疑问:如消息版本号这样的信息,一个short型就足够了,为什么要浪费两个字节呢(当时书中也提到了这个问题)?现在,我们就清楚了。
从图3.1的表格中还可以看到,消息定义中的int型与long型,在XDR中都是统一用xdr_int来处理的。
2.浮点型数
消息表示代码中的浮点型数是指消息各组成部分的代码中以float关键字定义的基本数据类型变量,从图3.1中可看出其XDR接口函数为xdr_ float,长度为4个字节。
3.双浮点型数与长整型数
双浮点型数及长整型数是指消息各组成部分的代码中以double关键字定义的基本数据类型变量,从图3.1中可以看出,其XDR接口函数为xdr_double,长度为8个字节。
应用系统程序员在定义一个新的消息功能类型时,如果在消息体的本体数据或列表数据中需要使用一个双浮点型数,则要用double来定义。但要注意的是,如果需要一个长整型数,也要用double来定义。
这里要指出的是:其实,在表示消息本体数据或列表数据的类中,一个基本数据类型的变量在定义时,只要其字节长度足够(超过也可以,但不能不够)即可,并不要求与该变量表示的数值类型完全相符合。但一定要在该类后面的消息体流化专有代码的宏定义部分,使用正确的接口。例如,在一个名为ExampleRequest的请求类型消息本体数据的类定义中,如果我们用float型定义了一个变量,但其实它应该是int型,这并没有太大关系,最关键的是需要在对应的流化专有代码中,在严格顺序位置指明:流化/反流化时是要采用MESSAGE_SEND_INT与MESSAGE_RECEIVE_INT(可参考2.6.3.1节的内容),这一点在数值型变量中体现的并不明显,但对后面的字符串型和二进制流型变量而言就显得格外重要了(因为在C/C++中,它们都以char[]来定义)。
4.字符串型
字符串型是消息表示与流化处理中最重要、最复杂的类型。以Linux/UNIX C/C++语言为例,我们的消息体系中只引入char Array字符串与std::string字符串两种类型来介绍。要注意的是,以上各个数值型在MESSAGE_SIZE_xxx的计算中,都已经满足了XDR关于4的倍数的要求,但字符串与二进制字节流型则都必须要自行处理长度的问题。
(1)char Array字符串
char Array字符串是指在消息表示代码中,用char[]定义的,用MESSAGE_ SEND_UTF8SZ与MESSAGE_RECEIVE_UTF8SZ宏定义表示流化/反流化接口的变量。
注意,纯粹的类变量定义部分的char []已经无法帮助我们确定该变量是字符串型,还是二进制流。
这里有一个小细节需要注意:在2.6.3节中,我们定义消息本体数据类ExampleRequest时,有一个变量variable2的定义为:
char variable2[21*3];
虽然我们说单纯从其char []的定义看不出来它是字符串还是二进制流,但从其21*3的长度定义其实也能看出一些端倪:这应该是一个字符串型变量,因为Linux/UNIX C/C++中的字符采用UTF-8编码,而UTF-8中一个字符最多用三个字节表示,因此,从variable2的长度定义可以猜出,这应该是想存储一个最多有20个字符的字符串。
当然,这种书写方式并不是必须的,完全可以写成char variable2[63],效果也是一样的。但在本书中,建议Linux/UNIX C/C++的消息表示代码中,采用类似规则来定义char[]型的字符串。
关于字节长度,在2.6.3节消息流化专有代码StreamlizeTemplate.h中,MESSAGE_SIZE_UTF8SZ的定义如下:
#define MESSAGE_SIZE_UTF8SZ(VAL) size += ar.getutf8szsize(VAL,(sizeof(VAL) ));
其中函数getutf8szsize是为了保证XDR“字节长度为4的倍数”的要求而产生的,定义在类XDRMethod中,后文我们会专门用示例代码介绍。
(2)std::string字符串
在Linux/UNIX的消息表示代码中,字符串也可以用std::string来定义,与char[]定义不同的是,这种定义可以更直观地看出来该变量代表一个字符串,但与以上char variable2[21*3]不同的是,std::string对应的流化专有代码应该是采用宏定义:MESSAGE_SIZE_STLUTF8,MESSAGE_SEND_STLUTF8,MESSAGE_RECEIVE_ STLUTF8,MESSAGE_CLEAR_STLUTF8。
2.6.3节消息流化专有代码StreamlizeTemplate.h中,MESSAGE_SIZE_ STLUTF8的定义如下:
#def ine MESSAGE_SIZE_STLUTF8(VAL) size += ar.getStlutf8Size(VAL);
其中函数getStlutf8Size也是为了保证XDR“字节长度为4的倍数”的要求而产生的,定义在类XDRMethod中,后文我们会专门以例示代码介绍。
3.3.3 二进制字节流
二进制字节流在Linux/UNIX C/C++的消息表示代码中,都采用char[]来定义变量,可以分为纯opaque字节流、双字节Wopaque字节流与含长度的字节流三种。
1.纯opaque字节流
是指定义为char[]型变量,但对应的流化专有代码采用xxx_OPAQUE宏定义的数据类型。在2.6.3节的流化公共代码StreamlizeTemplate.h中,关于xxx_OPAQUE的宏定义如下:
#define MESSAGE_SIZE_OPAQUE(LEN) if(((LEN) % 4) ==0){\ size += (LEN);\ }else{\ size += (LEN) + 4 - ((LEN) % 4);\ } #define MESSAGE_SEND_OPAQUE(VAL,LEN) ar.send_opaque ((VAL),LEN) #define MESSAGE_RECEIVE_OPAQUE(VAL,LEN) ar.receive_opaque((VAL),LEN) #def ine MESSAGE_CLEAR_OPAQUE(VAL) memset(VAL, 0, sizeof(VAL));
可以看出,二进制流的长度LEN必须在发送(流化)与接收(反流化)等接口定义时明确给出(当然,最初的长度是在定义新功能类型的消息时,在消息体流化专有代码部分给出的),并且已经满足了XDR为4的倍数的要求。这其实也很容易理解,否则,如何来控制发送与接收的界限呢?
2.双字节wopaque字节流
是指定义为char[]型变量,但对应的流化专有代码采用xxx_WOPAQUE宏定义的数据类型。wopaque流与纯opaque流的区别在于:wopaque流以每两个字节一个单元(即类似于VC++中提到了WORD),其主要用途是用以流化UTF-16编码的字节流。
在2.6.3节的流化公共代码StreamlizeTemplate.h中,关于xxx_ WOPAQUE的长度的宏定义如下(其他几个没什么特殊之处):
#def ine MESSAGE_SIZE_WOPAQUE(LEN) MESSAGE_SIZE_OPAQUE(LEN*UTF16_LEN)
其中UTF16_LEN为2,可见,在消息体定义的专有代码中,需要给出的wopaque流的长度是指UTF-16编码字符的个数(流字节长度的一半)。
3.含长度的字节流bytes
是指定义为char[]型变量,但对应的流化专有代码采用xxx_BYTES宏定义的数据类型。同样,我们看到StreamlizeTemplate.h的定义中,各个xxx_BYTES的定义如下:
#define MESSAGE_SIZE_BYTES(VAL) MESSAGE_SIZE_LONG \ MESSAGE_SIZE_OPAQUE(*((int32_t*)(VAL))) #define MESSAGE_SEND_BYTES(VAL) ar.send_bytes (((char *)(VAL) + 4), *((unsigned int *)(VAL))) #define MESSAGE_RECEIVE_BYTES(VAL) ar.receive_bytes((VAL) + 4, *((unsigned int*)(VAL)), sizeof((VAL)) - 4) #def ine MESSAGE_CLEAR_BYTES(VAL) memset(VAL, 0, sizeof(VAL));
我们注意到,在计算长度、发送(流化)与接收(反流化)时,都在二进制流最开始的地方留了一个4字节长度的空间,该空间正是存放该字节流的长度的。与上面opaque类型不同的是:bytes类型字节流不需要在消息体定义的专有代码中给出流长度,而是要求应用系统程序员在将字节流放入改变量之前,就将其长度以某种字序(Little-Endian或Big-Endian,后面我们可以看出,实际上是一个xdr_int的标准)放在字节流的最前面。另外,BYTES接口中对MESSAGE_SIZE_OPAQUE定义的复用,也保证了XDR关于4的倍数的要求。
需要指出的是,3.3节对消息定义基本数据类型的介绍是基于Linux/UNIX C/C++语言展开的,如果采用Java语言,则不尽相同,但类型却是严格一一对应的。