2.6 消息表示的面向对象实现
通过对以上内容的学习,相信读者对消息表示法已经有了较为全面的了解,读者现在应该可以自己设计消息流化后的结构,然后编写代码实现消息的代码表示。至于用什么语言来实现,则可以自由选择,但这里要指出的是,除非是需要与Java应用程序采用消息通信,一般情况下,建议消息体系采用C/C++语言来编写。其原因是:通常消息体系的实现除了逻辑设计以外,都非常注重效率,而C/C++语言的执行效率要远远高于Java等语言,特别是在TCP/IP及Socket底层通信方面。
虽然目前读者可能对消息表示法已经比较了解了,但是还有很多细节,是不可能通过文字的描述完全讲清楚的,必须通过代码示例。
因此,本节会以面向对象的语言C++为例,以真正实现为目标,从代码层面介绍消息表示法。本节的代码示例,不只是对上述内容的简单实现,而是还有很多新的内容加入。相信读者通过对本节内容的学习,会对前文介绍的消息表示法的知识有更加形象与深入地了解,同时会对自行设计开发消息体系有较大的帮助。消息表示的各个组成部分在流化后虽然有严格的先后顺序,但在其代码表示层面,却是体现在代码单元之间的相互调用与依赖关系上,并不是表现在先后顺序上。
顺便指出,本书后面每一章都会有以代码为例展示前面所讲的内容。
2.6.1 消息头
前文已经介绍了消息头应该包含的元素,并且指出消息头应该由一个独立的代码单元组成,那么,对C++语言来讲,就是头文件中一个类的定义。
该类中除了我们已经知道的几个元素作为成员变量以外,还有以下内容需要包含在该类中。
· 消息头元素发送与接收的索引号。该索引号记录组成消息头的四个元素发送与接收的索引,每发送/接收一个元素,该索引号加1。
· 消息头数据检验位。通过某种算法,综合消息头四个元素的值,计算一个检验位值,以验证数据接收的准确性。其实,在实际发送时,可以将本来应该发送的消息头的某个元素(如第一个元素版本号)替换为该检验位的值来发送,这样一来,我们还是发送了四个元素,但却包含了检验位,不需要在流化后的消息头组成中专门增加一个关于校验位的元素。
· 消息头的发送与接收接口及实现。由于消息头的代码表示是公共代码,对每一种消息来说,其结构都是一样的,所以在该代码单元中,不仅要定义接口,而且还可以用inline的方式对该接口进行实现。
以上新加的内容都应该包含在消息头的代码单元中,但却属于消息流化、发送与接收的内容,不是本章的范畴。但我们应该清楚的是,在容纳消息头的同一个代码单元中,不仅包含了消息表示法的内容,还包含了其他内容。而对这些内容,这里只给出接口定义,其详细实现细节将在后面相关的章节给出。
C++语言实现的消息头表示类Message_Header(Message_Header.h),示例代码如下:
#ifndef ___MESSAGE_HEADER_INCLUDED__ #define ___MESSAGE_HEADER_INCLUDED__ …… class Message_Header {
private: unsigned char check[4]; unsigned m_nSendIdx; unsigned m_nReceiveIdx; public: int VersionNo; long SequenceNo; long ContinueFlag; long DataLength; Message_Header(){ m_nSendIdx = m_nReceiveIdx = 0; } inline bool Send (……) {……} inline bool Receive(……) {……} } #end if //#ifndef ___MESSAGE_HEADER_INCLUDED__
以上代码中,只有public域中的四个变量VersionNo、SequenceNo、ContinueFlag、DataLength的定义是属于本章消息表示法的范围。有些读者对这里都用四个字节(32位平台上)的变量来表示可能有些不理解,这在第3章“消息的流化”中就会讲清楚。
2.6.2 消息类型块
消息类型块应该由两个独立的代码单元来表示,即表示请求消息类型块的代码单元与表示回复消息类型块的代码单元。
注意,本章我们都是用“代码表示”,而不是用“代码实现”,这是因为本章相关的代码都是关注其定义与声明的部分,而实现逻辑则都是后面章节的内容,即使这两者会经常出现在同一个代码单元中。
这两种不同响应类型的消息,其消息类型块应该包含的内容在前文已经讲过。当然,与消息头一样,在容纳其表示法的代码单元(自然,我们这里会用类定义来示例)中,同时还要容纳以下内容。
· 消息类型块发送与接收的索引号。该索引号记录组成消息类型块的组成元素发送与接收的索引,每发送/接收一个元素,该索引号加1。
· 消息类型块的发送与接收接口及实现。与消息头一样,由于这部分也是公共代码,所以在同一代码单元中,不仅要定义接口,而且还要用inline的方式对该接口进行实现。
采用C++语言实现的请求消息类型块表示的类Message_Request(Message_Request.h)示例代码如下:
#ifndef ___MESSAGE_REQUEST_INCLUDED___ #define ___MESSAGE_REQUEST_INCLUDED___ …… class Message_Request { private: unsigned m_nSendIdx; unsigned m_nReceiveIdx; public: int TypeAndFunction; int CallbackOperationType; int Flags; long MessageLength; Message_Request(){ m_nSendIdx = m_nReceiveIdx = 0; } inline bool Send (……) {……} inline bool Receive (……) {……} inline bool Receive_No_TypeAndFunction (……) {……} }; #end if //#ifndef ___MESSAGE_REQUEST_INCLUDED___
采用C++语言实现的回复消息类型块表示的类Message_Request(Message_Response.h)示例代码如下:
#ifndef ___MESSAGE_RESPONSE_INCLUDED___ #define ___MESSAGE_RESPONSE_INCLUDED___ …… class Message_Response { private: unsigned m_nSendIdx; unsigned m_nReceiveIdx;
public: int TypeAndFunction; long ErrorNo; int ErrorType; char ErrorString[SYSTEM_MAX_UNICODE_SIZE]; int Flags; long MessageLength; Message_Response(){ ErrorString[0] = 0; ……//some other things need to be defined here later on Flags = 0; m_nSendIdx = m_nReceiveIdx = 0; } inline bool Send (……) {……} inline bool Receive (……) {……} inline bool Receive_No_TypeAndFunction (……) {……} }; #end if //#ifndef ___MESSAGE_RESPONSE_INCLUDED___
同样,以上代码中,只有类public域中的变量是属于消息表示法的内容。我们注意到,正如前文所说,类Message_Request中,有四个元素,类Message_Response中,有六个元素,其中ErrorString的长度我们定义为SYSTEM_MAX_UNICODE_SIZE,是指本消息体系宏定义的最大UNICODE字符字节长度(而不是字符长度,如果是UTF-8,则其最少可容纳的字符长度为SYSTEM_MAX_UNICODE_SIZE/3-1)。并且,还要清楚一点,在流化后的消息类型块中,对回复消息来讲,其元素ErrorString与Flags根据具体情况不同,可能包含,也可能不包含。
同样,关于消息类型块发送与接收接口的具体实现,将在后面相关章节看到。关于Receive_No_TypeAndFunction接口的定义,是指在接收时,可以根据需求跳过容纳消息类型编号与消息功能类型编号的变量,因为多数情况下,该变量需要在正式接收消息类型块之前首先被单独接收到。
2.6.3 消息体
消息体的代码表示较为复杂,从上述内容可以看到,主要有五个代码单元涉及。
(1)消息体定义专有代码,其中包括:
· 消息描述信息定义
· 消息本体数据定义
· 消息列表数据定义
· 消息创成接口之专有代码
· 消息流化接口之专有代码
(2)消息体定义公共模板;
(3)消息创成接口之消息创建公共模板;
(4)消息创成接口之请求操作类型与消息构成关系定义公共模板;
(5)消息流化接口之公共模板。
下面我们分别介绍。
1.消息体定义专有代码
消息体定义的专有代码部分,是指应用系统程序员需要定义新功能类型的消息时需要新编写制作的代码单元,它包括了消息描述信息定义,消息本体数据定义,消息列表数据定义,消息创成接口之专有代码,消息流化接口之专有代码等部分。
我们将该代码单元命名为Message_[消息名称].h(下面代码中统一用Example表示),其中包括了一些该消息专用的宏定义与几个类class的定义。
#ifndef __MESSAGE_EXAMPLE_INCLUDED__ #define __MESSAGE_EXAMPLE_INCLUDED__ //第一部分:消息描述信息定义 #define MESSAGE_EXAMPLE _VERSION 1 //消息版本号 #define MESSAGE_EXAMPLE_NUMBER 102 //消息功能类型编号 enum ExampleRequestOperations{ Request_ExampleOperation1 = 0, Request_ExampleOperation2 = 1, Request_ExampleOperation2 = 2, ……
}; //消息请求操作类型定义 #define EXAMPLE_REQUEST Message_Template<ExampleReqeust, ExampleList> //请求消息体构成模板定义(Message_Template是消息体定义公共模板,在后文介绍) #define EXAMPLE_RESPONSE Message_Template<ExampleResponse, ExampleList> //回复消息体构成模板定义 //第二部分(1):请求消息本体数据定义 class ExampleReqest : public StreamlizeTemplate{ public: ExampleRequest(){ } int variable1; char variable2[21*3]; …… //第二部分(2):请求消息流化接口之专有代码 void clear(){ MESSAGE_CLEAR_INT(variable1) MESSAGE_CLEAR_UTF8SZ(variable2) …… } BEGIN_MESSAGE_SEND MESSAGE_SEND_INT(variable1) MESSAGE_SEND_UTF8SZ(variable2) …… END_MESSAGE_SEND BEGIN_MESSAGE_RECEIVE MESSAGE_ RECEIVE _INT(variable1) MESSAGE_ RECEIVE _UTF8SZ(variable2) …… END_MESSAGE_RECEIVE BEGIN_MESSAGE_GETSIZE MESSAGE_GETSIZE_INT(variable1) MESSAGE_GETSIZE_UTF8SZ(variable2) …… END_MESSAGE_GETSIZE }; //第三部分(1):回复消息本体数据定义 class ExampleResponse : public StreamlizeTemplate{
public: ExampleResponse(){ } int variable1; char variable2[21*3]; …… //第三部分(2):回复消息流化接口之专有代码 void clear(){ MESSAGE_CLEAR_INT(variable1) MESSAGE_CLEAR_UTF8SZ (variable2) …… } BEGIN_MESSAGE_SEND MESSAGE_SEND_INT(variable1) MESSAGE_SEND_UTF8SZ (variable2) …… END_MESSAGE_SEND BEGIN_MESSAGE_RECEIVE MESSAGE_ RECEIVE _INT(variable1) MESSAGE_ RECEIVE _UTF8SZ (variable2) …… END_MESSAGE_RECEIVE BEGIN_MESSAGE_GETSIZE MESSAGE_GETSIZE_INT(variable1) MESSAGE_GETSIZE_UTF8SZ (variable2) …… END_MESSAGE_GETSIZE }; //第四部分(1):消息列表数据定义 class ExampleList : public StreamlizeTemplate{ public: ExampleList(){ } long variable1; char variable2[1024]; …… //第四部分(2):消息列表数据流化接口之专有代码
void clear(){ MESSAGE_CLEAR_INT(variable1) MESSAGE_CLEAR_BYTES(variable2) …… } BEGIN_MESSAGE_SEND MESSAGE_SEND_INT(variable1) MESSAGE_SEND_BYTES(variable2) …… END_MESSAGE_SEND BEGIN_MESSAGE_RECEIVE MESSAGE_ RECEIVE_INT(variable1) MESSAGE_ RECEIVE_BYTES(variable2) …… END_MESSAGE_RECEIVE BEGIN_MESSAGE_GETSIZE MESSAGE_GETSIZE_INT(variable1) MESSAGE_GETSIZE_BYTES(variable2)…… END_MESSAGE_GETSIZE }; //第五部分:消息创成接口之专有代码 #defineDEFINE_EXAMPLE_CREATE MESSAGE_CREATOR_DEFINE(102,1,ExampleRequest,ExampleResponse,ExampleLis t) //消息体创建专有接口 #defineDEFINE_EXAMPLE_OPERATIONS MESSAGE_OPERATION_DEFINE(102,1,0,MSG_SEND_LIST) \ MESSAGE_OPERATION_DEFINE(102,1,1,MSG_SEND_PRIMEBODY|MSG_SEND_LIST) //请求操作类型与消息体构成关系定义专有代码
注意,以上代码中用于定义请求与回复消息体的模板类Message_ Template与消息本体数据与列表数据类的基类StreamlizeTemplate分别是消息体定义公共模板与消息流化接口的公共代码,后面将详细介绍。
从以上代码还可以看到,在C/C++语言中,对消息本体数据或消息列表数据的基本元素,虽然都用char类型表示,但在消息流化接口定义中,也可以给出不同的实现:对请求消息本体数据中的varaible2,我们是当成一个可以容纳21*3个字节的字符串来实现发送与接收的(后文可以看到, WSTRING流化后是UTF-16格式的字符串流),而对消息列表数据中的variable2,我们则是当成固定长度的二进制字节流来发送与接收的(BYTES),即不存在编码转换的问题。
2.消息体定义公共模板
流化后,每个消息体都是由请求类或回复类消息本体数据与消息列表数据组成,而在代码表示中,也明确包括这两个部分。
我们现在需要实现这样一种代码设计:在一个公共的代码单元中,以类似于参数化的方式定义出消息体组成的模板来,应用系统程序员在制造新的消息类型时,只需要将具体的请求/回复消息本体与列表代码单元的标志符代入参数,就可以成功定义一个新的消息类型。
很明显,这样的代码复用设计,在Java中可以采用接口(interface)或抽象类(abstract)来实现,而在C++中可以使用模板(template)、宏定义、虚函数等结合来实现。它是一个独立的代码单元,在这个代码单元中,还包括了一些公共的接口函数定义与实现(inline方式),包括发送、接收、获取版本号、获取消息功能类型、设置/获取消息处理标志、设置/获取消息序列号等。消息体定义公共模板Message_Template.h示例代码如下:
#ifndef ___MESSAGE_TEMPLATE_INCLUDED__ #define ___MESSAGE_TEMPLATE_INCLUDED__ #define MSG_PRIMEBODY #define MSG_LIST class Abstract_Message_Template { private: int m_nMessageNumber; int m_nMessageVersion; long m_MessageFlags; protected: ……//一些与消息管理有关的代码,这里略去 long m_nSequenceNo; void do_clone(Abstract_Message_Template &s){
…… } public: …… inline int getMessageNumber(){return m_nMessageNumber;} inline int getMessageVersion(){return m_nMessageVersion;} inline long getMessageFlags() { return m_MessageFlags;} inline void setMessageFlags(long v){ m_MessageFlags = v;} inline void setSequenceNo(long seq_no){ m_nSequenceNo = seq_no;} inline long getSequenceNo(){ return m_nSequenceNo;} Abstract_Message_Template(int nr, int ver):m_nMessageNumber(nr), m_nMessageVersion (ver) { m_MessageFlags = 0; /* no flags */ …… } CMessageError m_Error; virtual bool Send (XDRMethod & ar, int flags) = 0; virtual bool Receive (XDRMethod & ar, int flags) = 0; virtual long GetSize (XDRMethod & ar, int flags) = 0; virtual bool ReceiveMore (XDRMethod & ar) = 0; virtual unsigned int _get_list_size() = 0; virtual unsigned int _get_primebody_size() = 0; virtual unsigned int _get_size() = 0; virtual Abstract_Message_Template *clone() = 0; virtual Abstract_Message_Template *clone(Abstract_Message_Template *)= 0; virtual ~ Abstract_Message_Template() {} }; typedef enum _tag_send_stages{ STAGE_PRIMEBODY, STAGE_LIST_SIZE, STAGE_LIST
} send_stages; template < class PRIMEBODY, class LIST > class Message_Template:public Abstract_Message_Template{ private: send_stages m_nSendStage; unsigned m_nSendListCount; send_stages m_nReceiveStage; int m_nReceiveListCount; LIST m_sListStructure; public: std::vector<LIST> m_vList; PRIMEBODY m_PrimeBody; Message_Template (int Nr, int Ver):Abstract_Message_Template(){……} virtual Abstract_Message_Template *clone(){……} virtual Abstract_Message_Template *clone(Abstract_Message_Template*v) {……} virtual long GetSize (……) {……} virtual bool Send (……) {……} virtual bool Receive (……) {……} virtual bool ReceiveMore (……) {……}//专门接收一个消息分多部分发送的情况 virtual ~ Message_Template() { m_vList.clear(); } …… }; #end if //#ifndef ___MESSAGE_TEMPLATE_INCLUDED__
可见,消息体定义公共模板代码是由C++的模板类template语法实现的;而从后面的代码示例可以看出,其他的公共模板(消息创成接口与消息流化接口的公共模板)又都是采用C++的宏定义来实现的。读者可以自行灵活选择使用这些方法来实现自己的设计思想。
3.消息创成接口之消息创建公共模板
我们可以看到,在上面的消息体定义专有代码中,有消息创建的部分(第五部分):该部分代码也是参数化的形式(但采用宏定义方式表达),由应用系统程序员定义新的消息功能类型时,填入关于该具体消息的信息(包括消息功能编号,版本号,请求类消息本体类名,回复类消息本体类名和消息列表类名五个要素)即可。
很明显,这个参数化定义的模板,应该是由一个公共的代码单元来实现的,我们称之为“消息创成接口之消息创建公共模板”,该代码单元可以是一个名为Message_Creator.h的头文件,其中,用于创建消息体的代码,我们也用class Message_Creator来实现,该类中包含了任意一种功能类型的消息体在消息体系运行过程中被动态创建的公共模板。
熟悉面向对象语言的读者应该知道,能实现这种目标的代码,在Java中可以用接口(interface)或抽象类/抽象方法来实现;而在C++中,则可以用具有多形性(Polymophism)特性的纯虚函数(pure virtual)及宏定义结合实现(可以看出,借助template或宏定义技术可以实现参数化的代码调用)。
用C++实现的Message_Creator.h的示例如下所示:
#ifndef ___MESSAGE_CREATOR_INCLUDED___ #define ___MESSAGE_CREATOR_INCLUDED___ …… class Message_Creator { public: Message_Creator(){} /*Create a response structure for the specified message version */ inline Abstract_Message_Template *CreateResponse(int number, int version) { return Create(number, version,FLAG_CREATE_RESPONSE); } /*Create a request structure for the specified message version */ inline Abstract_Message_Template *CreateRequest(int number, int
version) { return Create(number, version,FLAG_CREATE_REQUEST); } /* Virtual destructors */ virtual ~ Message_Creator() {} protected: virtual Abstract_Message_Template * Create (int number, int version, int flags)= 0; }; #define MESSAGE _CREATOR_START_IMPLEMENT(clname) \ Abstract_ Message_Template *clname::Create(int number, int version, int flags){\ switch (number){ \ ……//该代码单元未完成的部分,在消息注册的代码单元中补上 #define MESSAGE_CREATOR_DECLARE protected:\ virtual Abstract_Message_Template *Create(int number, int version, int flags); #define MESSAGE_CREATOR_START protected:\ virtual Abstract_Message_Template *Create (int number, int version, int flags){\ switch (number){ \ ……//该代码单元未完成的部分,在消息注册的代码单元中补上 #define MESSAGE_CREATOR_DEFINE(NR,VER,REQUEST,RESPONSE,LIST) case (NR) :\ if (version == (VER)){\ if (flags == FLAG_CREATE_REQUEST){\ return new Message_Template <REQUEST ,LIST >((NR),(VER));\ }else{\ return new Message_Template <RESPONSE ,LIST >((NR),(VER));\ }\ }else{\ return NULL;\ }\ break;
#define MESSAGE_CREATOR_END default:\ return NULL;\ break;\ }\ } #end if //#ifndef ___MESSAGE_CREATOR_INCLUDED___
细心的读者可能发现,在这个Message_Creator.h中,除了包括消息创建的基类(抽象接口)Message_Creator以外,还由若干个宏定义(MESSAGE_ CREATOR_DECLARE,MESSAGE_CREATOR_START, MESSAGE_CREATOR_ DEFINE, MESSAGE_ CREATOR_END)组成了一个新的函数实现,该函数便是用来对抽象类Message_Creator中的纯虚函数Create()在其子类中进行具体实现(重载)用的。
由此不难看出,有了Message_Creator.h以后,应用系统程序员每次在定义一个新的消息功能类型时,除了像上面示例的那样,需要在2.6.3.1节消息体定义专有代码中编写第五部分消息体创建专有接口代码MESSAGE_CREATOR_ DEFINE(NR,VER,REQUEST,RESPONSE,LIST)以外,还需要在某个地方实现如下宏定义:
MESSAGE_CREATOR_DECLARE MESSAGE_CREATOR_START DEFINE_EXAMPLE1_CREATOR //1号消息体创建定义 DEFINE_EXAMPLE1_CREATOR //2号消息体创建定义 …… MESS AGE_CREATOR_END
应该注意到,DEFINE_EXAMPLE1_CREATOR,DEFINE_EXAMPLE2_ CREATOR正是在2.6.3.1节第五部分“消息体创建专有代码”中定义的。正如前文2.5.6节所讲,在本节关于消息创成接口的宏定义,实际上只有与上述宏定义一起时才能完成一个完整的、可编译的消息体创建代码,而这部分代码,便是我们后面要提到的消息注册。虽然我们选择了在第4章“消息注册”中重点介绍消息注册的内容,但其实它也应该属于本章消息表示法的内容范围。
很明显,另一个与MESSAGE_CREATOR_START类似的宏定义MESSAGE_ CREATOR_START_IMPLEMENT是在当要求消息体创建的代码单元不采用内联(inline)方式实现时(即.h与.cpp分开),完成同样的目的。
4.消息创成接口之请求操作与构成关系公共模板
在上面的消息体定义专有代码中的第五部分,除了有关消息创建的部分以外,还有关于请求操作类型与消息构成关系定义的专有代码。这部分专有代码,也是以宏定义的方式,实现通用的参数化定义。对每一个请求操作类型,都有一个定义,每一个定义,都以消息功能编号、版本号、请求操作类型号和对应的消息处理标志(可以BIT与的方式组合多个标志)为参数。
那么,同样也应该有代码单元用于描述该内容的公共模板,以实现该参数化的定义。本书中,可以将该代码单元示例为一个名为Message_Operation.h的头文件,其中,用于获取请求操作与消息体构成关系(实际上通过消息处理标志Flags表示)的代码,我们用class Message_Operation来实现,该类中包含了一个纯虚函数,用于定义通过不同请求操作类型获取相应消息处理标志Flags的接口,而消息体构成定义就是由消息处理标志Flags来描述的。其他部分代码则与消息创建类似,是用于实现参数化的宏定义。Message_Operation.h如下所示:
#ifndef ___MESSAGE_OPERATION_INCLUDED__ #define ___MESSAGE_OPERATION_INCLUDED__ …… class Message_Operation { public: virtual int GetFlags(int ApiNumber, int ApiVersion, int Operation) = 0; virtual ~Message_Operation(){} }; #define MESSAGE_OPERATION_START_IMPLEMENT(clname) int clname::virtual int GetFlags(int ApiNumber, int ApiVersion,int Operation){ #define MESSAGE_OPERATION_DECLARE virtual int GetFlags(int ApiNumber, int ApiVersion,int Operation); #define MESSAGE_OPERATION_START virtual int GetFlags(int ApiNumber, int ApiVersion,int Operation){
#define MESSAGE_OPERATION_DEFINE (NR,VER,OPERATION,FLAGS) if (Number == (NR) && Version == (VER) && Operation == (OPERATION)) { return (FLAGS);} #define MESSAGE_OPERATION_MANAGER_END return OPERATION_UNKNOWN;} #end if //#ifndef ___MESSAGE_OPERATION_INCLUDED__
与上面消息创建定义类似,这里由若干个宏定义(MESSAGE_OPERATION_
DECLARE,MESSAGE_OPERATION_START, MESSAGE_OPERATION_ DEFINE, MESSAGE_OPERATION_END)组成了一个新的函数实现,该函数便是用来对抽象类Message_Operation中的纯虚函数GetFlags()在其子类中进行具体实现(重载)用的。
上面代码中的消息处理标志Flags,其实是由四个位标志宏定义通过按位与(C语言中的“|”)操作组合而成的,它们是:
MESSAGE_SEND_PRIMEBODY //该请求操作需要发送消息本体数据 MESSAGE_SEND_LIST //该请求操作需要发送消息列表数据 MESSAGE_RECEIVE_PRIMEBODY //该请求操作需要接收消息本体数据 MESS AGE_RECEIVE_LIST //该请求操作需要接收消息列表数据
后文中我们还可以看到,在实际的消息发送与接收中,应用系统程序员还可以根据需要对该标志进行扩充(一个四字节变量表示的Flags还有32-4=28个可扩充标志的能力),如消息发送优先级等,这也是我们将该标志统称为“消息处理标志”的原因。
同样,有了Message_Operation.h以后,应用系统程序员每次在定义一个新的消息功能类型时,除了需要在2.6.3.1节消息体定义专有代码中编写第五部分请求操作类型与消息构成关系定义部分专有接口代码MESSAGE_OPERATION_ DEFINE(NR,VER,OPERATION,FLAGS)外,还需要在某个地方实现如下定义:
MESSAGE_OPERATION_DECLARE MESSAGE_ OPERATION _START DEFINE_ EXAMPLE1 _OPERATION //1号消息操作类型与构成关系定义 DEFINE_ EXAMPLE2 _OPERATION //2号消息操作类型与构成关系定义 …… MESS AGE_ OPERATION _END
只有这样才能完成一个完整的消息请求操作类型与构成关系定义的代码实现,而这部分代码同样也是消息注册范畴的内容。同时应该注意到, DEFINE_EXAMPLE1_OPERATION,DEFINE_EXAMPLE2_OPERATION正是在消息体定义专有代码第五部分“请求操作类型与消息体构成关系定义专有代码”中定义的。
5.消息流化接口公共模板
在消息体定义专用代码示例中,有请求/回复消息本体数据及消息列表数据流化接口专有代码的定义,很明显,那些代码同样也是对某些以宏定义方式实现的参数化模板的填充。现在很容易可以想到,需要有代码单元实现这些通用的参数化模板,这即是本节的内容“消息流化接口公共模板”。
不难看出,消息流化接口公共模板的代码单元中的主要内容是对各种基本数据类型流化处理接口的定义,包括发送,接收,清零与计算大小。在消息体定义专用代码中,描述消息本体数据与列表数据的类class都是从一个名叫StreamlizeTemplate的基类中继承而来的,而该基类就是本节要介绍的消息流化接口公共模板。
我们可以将该代码单元放在头文件StreamlizeTemplate.h中,其中定义了一个基类class StreamlizeTemplate,示例如下:
#ifndef ___GENERIC_SREAMLIZE_TEMPLATE_INCLUDED___ #define ___GENERIC_ SREAMLIZE_TEMPLATE _INCLUDED___ …… #include "XDRMethod.h" class StreamlizeTemplate{ protected: /* strange naming convention to avoid clashes with child classes */ unsigned _M_nSendIndex; unsigned _M_nReceiveIndex; public: virtual ~ StreamlizeTemplate (){}; …… virtual bool __Send (XDRMethod & ar)=0; virtual bool __Receive (XDRMethod & ar)=0; virtual long __GetSize (XDRMethod & ar)=0; inline long Size_long (XDRMethod & ar){return SIZE_LONG;} inline long Size_int (XDRMethod & ar){return SIZE_INT;}
inline long Size_float (XDRMethod & ar){return ar.SIZE_FLOAT;} inline long Size_double (XDRMethod & ar){return ar.SIZE_DOUBLE;} inline long Size_psz (XDRMethod & ar, const char *str, int maxlen) { long len = maxstrlen(str,maxlen);//从maxlen与strlen中到小者 if ((len % 4) != 0) { len += (4 - (len % 4)); } len += SIZE_INT; return len; } }; #define SET_OPAQUE_SIZE(BUFF,SIZE) *((int32_t*)(BUFF)) = (SIZE) #define GET_OPAQUE_SIZE(BUFF) (*((int32_t*)(BUFF))) //消息发送接口定义 #define BEGIN_MESSAGE_SEND bool __Send(XDRMethod &ar){ #define MESSAGE_SEND_LONG(VAL) ar.send_long (VAL) #define MESSAGE_SEND_INT(VAL) ar.send_int (VAL) #define MESSAGE_SEND_UTF8SZ(VAL) ar.send_utf8sz (VAL, sizeof(VAL)) #define MESSAGE_SEND_STLUTF8(VAL) ar.send_stlutf8 (VAL) #define MESSAGE_SEND_DOUBLE(VAL) ar.send_double (VAL) #define MESSAGE_SEND_FLOAT(VAL) ar.send_float (VAL) #define MESSAGE_SEND_OPAQUE(VAL,LEN) ar.send_opaque ((VAL),LEN) #define MESSAGE_SEND_WOPAQUE(VAL,LEN) ar.send_wOpaque ((VAL), LEN) #define MESSAGE_SEND_BYTES(VAL) ar.send_bytes (((char *)(VAL)+ 4), *((unsigned int *)(VAL))) #define END_MESSAGE_SEND return true; } //消息清空接口定义,一般在消息体定义专用代码的构造函数中调用 #define MESSAGE_CLEAR_LONG(VAL) VAL = 0; #define MESSAGE_CLEAR_INT(VAL) VAL = 0; #define MESSAGE_CLEAR_UTF8SZ(VAL) VAL[0] = VAL[1] = '\0'; #define MESSAGE_CLEAR_STLUTF8(VAL) VAL.clear(); #define MESSAGE_CLEAR_DOUBLE(VAL) VAL = 0; #define MESSAGE_CLEAR_FLOAT(VAL) VAL = 0; #define MESSAGE_CLEAR_OPAQUE(VAL) memset(VAL, 0, sizeof(VAL)); #define MESSAGE_CLEAR_WOPAQUE(VAL) memset(VAL, 0, sizeof(VAL)); #define MESSAGE_CLEAR_BYTES(VAL) memset(VAL, 0, sizeof(VAL));
//消息接收接口定义 #define BEGIN_MESSAGE_RECEIVE bool __Receive(XDRMethod &ar){ #define MESSAGE_RECEIVE_LONG(VAL) ar.receive_long(VAL) #define MESSAGE_RECEIVE_INT(VAL) ar.receive_int(VAL) #define MESSAGE_RECEIVE_STLUTF8(VAL) ar.receive_stlutf8((VAL),len) #define MESSAGE_RECEIVE_DOUBLE(VAL) ar.receive_double(VAL) #define MESSAGE_RECEIVE_FLOAT(VAL) ar.receive_float(VAL) #define MESSAGE_RECEIVE_OPAQUE(VAL,LEN) ar.receive_opaque((VAL),(LEN)) #define MESSAGE_RECEIVE_WOPAQUE(VAL,LEN) ar.receive_wOpaque((VAL), (LEN)) #define MESSAGE_RECEIVE_UTF8SZ_LEN(VAL,LEN,MAXLEN) ar.receive_utf8sz ((VAL), (LEN), MAXLEN) #define MESSAGE_RECEIVE_BYTES(VAL) ar.receive_bytes((VAL) + 4, *((unsigned int*)(VAL)), sizeof((VAL)) - 4) #define END_MESSAGE_RECEIVE return true;} //消息大小接口定义 #define BEGIN_MESSAGE_GETSIZE long __GetSize(XDRMethod &ar){\ long size = 0; #define MESSAGE_SIZE_LONG size += Size_long (ar); #define MESSAGE_SIZE_INT size += Size_int (ar); #define MESSAGE_SIZE_UTF8SZ(VAL) size += ar.getutf8sz(VAL,(sizeof(VAL)));#define MESSAGE_SIZE_STLUTF8(VAL) size += ar.getStlutf8Size(VAL); #define MESSAGE_SIZE_DOUBLE size += Size_double(ar); #define MESSAGE_SIZE_FLOAT size += Size_float(ar); #define MESSAGE_SIZE_BYTES(VAL) MESSAGE_SIZE_LONG \ MESSAGE_SIZE_OPAQUE(*((int32_t*)(VAL))) #define MESSAGE_SIZE_OPAQUE(LEN) if (((LEN) % 4) ==0){\ size += (LEN);\ }else{\ size += (LEN) + 4 - ((LEN) % 4);\ } #define MESSAGE_SIZE_WOPAQUE(LEN) MESSAGE_SIZE_OPAQUE(LEN*UTF16_LEN) #define END_MESSAGE_GETSIZE return size;} #end if //#ifndef ___GENERIC_STREAMLIZE_TEMPLATE_INCLUDED___
从StreamlizeTemplate.h的代码中可以看到:
· 消息体定义专有代码中的所有流化接口专用代码涉及的通用模板(各种基本数据类型发送/接收等)在这里都得到了定义。
· 其中另一个头文件XDRMethod.h及许多类XDRMethod的实例ar中的函数(如ar.send_long, ar.send_opaque()等),是指如果这里采用XDR方法来实现真正的发送/接收流化过程时需要用到的底层接口,这将在第3章“消息的流化”中专门介绍(XDR接口是实现平台无关的重要方法)。可以看出,StreamlizeTemplate.h中只是集中定义了不同基本数据类型流化/反流化的公共接口,用以实现消息体流化专有代码中严格按顺序排列的各个消息体内容的宏定义,而真正的流化/反流化实现,则还是定义类XDRMethod代码单元的责任。
· 这里,读者只需要对WSTRING、WOPAQUE、OPAQUE、UTF8SZ等扩展的基本数据类型有个初步了解即可,第3章“消息的流化”会详细介绍它们的含义和用途。
6.关于消息注册的说明
读者应该已经注意到,到目前为止,所介绍的消息表示法内容都还只是对单个功能类型的消息各组成部分的代码定义(包括公共代码部分与专有代码部分)。例如,本章消息体定义专有代码中关于消息体创建的部分:
#defineDEFINE_EXAMPLE_CREATE MESSA GE_CREATOR_DEFINE(102,1,ExampleRequest,ExampleResponse,ExampleList)
我们知道这是对版本1的102号消息创建接口的宏定义,读者如果继续由此宏定义追踪到本章关于消息创建接口公共代码的宏定义部分,会发现这部分定义其实只是一个switch…case…结构中的一部分代码,而最终它应该属于一个用于消息创建的函数定义。不难想到,需要有一个代码单元专门用于容纳该函数的定义。
前文我们已经多次提到消息注册的概念,实际上,对每一个需要用到消息体系的服务(独立网络应用),都有消息注册的代码单元。而消息体系中专门用于实现消息体创建的函数定义与用于生成请求操作类型与构成关系定义标志的函数定义(我们统称为消息创成函数),其实就是消息注册的代码单元,而将一个新功能类型的消息中有关消息创成的专有代码宏定义加入到该代码单元的过程就称为消息注册。
毫无疑问,每定义一个新功能类型的消息,都需要在该代码单元中进行消息注册。关于这部分内容,从严格意义上讲,应该还是属于“消息表示法”的范畴,但由于消息注册的代码单元中其实包括了所有功能类型的消息组成定义,而本章其他内容都只涉及同一种功能类型消息;另外,后文我们会讲到,消息注册的代码单元其实并不是消息体系本身的内容,而是应用系统程序员基于消息体系构建独立网络应用时需要设计制作的内容,因此,本书将消息注册的相关内容在第4章以独立章节来介绍。这里需要指出的是,实际上,在本书的代码示例中,消息注册的代码单元就是对上述class Message_Creator与class Message_Operation进行继承的子类,其内容主要是关于一系列不同功能类型消息的宏定义集合。
2.6.4 消息表示法代码结构
至此,我们已经介绍完了消息表示法的所有内容。可以发现,其内容太多,各要素与代码单元之间的关系很复杂,如果不给出一个整体的、形象的总体描述,则很难完全掌握。
下面我们对面向对象方法实现消息表示法后的各个示例代码单元及其之间的关系进行图示,如图2.3所示,以帮助读者理解。
图2.3 消息表示法代码结构类图
图2.3中,有一点需要说明:“消息创成接口专用代码”与类“Message_ Creator”及“Message_Operation”之间的“依赖关系”图示并不准确,在我们的示例代码中,它们之间其实是C语言的宏定义相互依存的关系(读者也可以用其他方法实现),这里我们暂用UML的依赖关系图演示,希望读者不要误解。