2.3 业务消息格式框架
不论使用二进制消息格式还是文本消息格式例如XML消息,消息分发框架都是建立在固定的消息头内容的基础之上的,因为只要解析出消息头就完全可以进行消息分发了。一般来说,消息头至少包含以下内容:消息类型、消息ID、消息序列号。为了满足将来业务功能的扩展需要(比如可令服务器作为一个消息转发中心,将某些前台客户端发来的业务消息转发给另外一些后台客户端,后台客户端作为业务服务器,消息转发服务器又可称为中央服务器),有必要为消息头增加源客户端ID和目的客户端ID。
2.3.1 消息头结构
因此,我们将消息头对应的结构定义为:
class MessageHeader { public: MessageHeader(); const MessageType& GetMessageType() const; void SetMessageType(const MessageType& msgType); ClientID GetSource() const; void SetSource(ClientID src); ClientID GetDestination() const; void SetDestination(ClientID dest); SerialNumber GetSn() const; void SetSn(SerialNumber sn); const MessageID& GetMessageID() const; void SetMessageID(const MessageID& msgId) protected: MessageType m_msgType; // 消息类型 ClientID m_srcClientId; // 消息源客户端ID ClientID m_destClientId; // 目的客户端ID SerialNumber m_sn; // 请求/应答消息的序列号 MessageID m_msgId; // 消息ID }; typedef SmartPtr<MessageHeader> MessageHeaderSmartPtr; |
客户端ID和消息序列号都为整数,具体类型及其分配或获取方法应由应用层的业务逻辑按需要而定。消息类型和消息ID可以使用整数值或者字符串常量来表示,当然得根据用户选择使用文本消息格式还是二进制消息格式而定。
为了适应用户的需要,该框架支持XML业务消息格式和二进制业务消息格式,并用一个预定义宏来配置,用户可以在这两种消息格式之间静态切换,或者增加第三种消息格式,但是一个系统中只能使用其中一种格式:
#if 1 #ifndef _USE_XML_MESSAGE_FORMAT_ #define _USE_XML_MESSAGE_FORMAT_ #endif #else // 0 #ifndef _USE_BINARY_MESSAGE_FORMAT_ #define _USE_BINARY_MESSAGE_FORMAT_ #endif #endif // 1 |
因为本框架同时支持两种业务消息格式,所以后续的实现将依赖于这两个宏定义。由于二进制消息格式存在多字节数据字节序转换的问题,比较繁琐,所以本框架默认采用XML消息格式。如果用户想支持二进制消息格式,只需切换上述宏定义后重新编译客户端和服务器端的框架层即可。具体请参见头文件HYQ_framework_config.h。
本框架支持的消息类型、部分消息ID以及基本数据类型定义如下:
#ifdef _USE_XML_MESSAGE_FORMAT_ // 消息类型取值范围 const char * const _UNKNOWN_MESSAGE_TYPE_ = ""; const char * const MESSAGE_TYPE_REQUEST = "request"; // 请求消息 const char * const MESSAGE_TYPE_ACK = "ack"; // 应答消息 const char * const MESSAGE_TYPE_NOTIFY = "notify"; // 通知消息 // 要求中央服务器从一个客户端广播到其他客户端 const char * const MESSAGE_TYPE_BROADCAST = "broadcast";// 广播消息 const char * const MESSAGE_TYPE_LOCAL = "local"; // 本地产生的消息
typedef STD::string MessageType; // 消息类型 typedef STD::string ServiceLayerMessage; // 消息的数据类型
// 公共消息ID const char * const _UNKNOWN_MESSAGE_ID_ = ""; const char * const LOCAL_MESSAGE_LINKDOWN = "LINKDOWN"; const char * const LOCAL_MESSAGE_LINKUP = "LINKUP"; const char * const LOCAL_MESSAGE_TIMER1SECOND = "TIMER1SECOND"; // 用户在这里添加自定义业务消息ID const char * const SERVICE_MESSAGE_X = "xxxx"; const char * const SERVICE_MESSAGE_A = "aaaa"; ……
typedef STD::string MessageID; // 字符串常量作为消息ID #elif defined(_USE_BINARY_MESSAGE_FORMAT_) // 消息类型取值范围 enum _enum_MessageTypeCode { _UNKNOWN_MESSAGE_TYPE_ = 0x00, MESSAGE_TYPE_REQUEST = 0x01, // 请求消息 MESSAGE_TYPE_ACK = 0x02, // 应答消息 MESSAGE_TYPE_NOTIFY = 0x03, // 通知消息 MESSAGE_TYPE_LOCAL = 0x04, // 本地产生的消息 MESSAGE_TYPE_BROADCAST = 0x05, // 广播消息 };
typedef _enum_MessageTypeCode MessageType; // 消息类型 typedef STD::vector<BYTE> ServiceLayerMessage; // 消息的数据类型
enum _enum_MessageID { // 公共消息ID _UNKNOWN_MESSAGE_ID_ = 0x00000000, LOCAL_MESSAGE_LINKDOWN = 0x00000001, LOCAL_MESSAGE_LINKUP = 0x00000002, LOCAL_MESSAGE_TIMER1SECOND = 0x00000003, // 用户在这里添加自定义业务消息ID(0x00000200~0x0000FFFF) SERVICE_MESSAGE_X = 0x00000200, SERVICE_MESSAGE_A = 0x00000201, ……
};
typedef _enum_MessageID MessageID; // 整数作为消息ID #endif
typedef USHORT SerialNumber; // 消息序列号类型 typedef INT ClientID; // 客户端ID类型 typedef STD::list<ClientID> ClientIDList; // 客户端ID列表类型 typedef SmartPtr<ClientIDList> ClientIDListSmartPtr; |
另外还有一些基本的常量定义如下:
enum { INVALID_CLIENT_ID = -1, CLIENT_ID_FOR_CENTRAL_SERVER = 0, // 中央服务器的ID为0 }; enum { DEFAULT_MESSAGE_SERIAL_NUMBER = 1, }; |
中央服务器的ID固定设置为0。如果客户端A的某个消息是发送给另一个客户端B的,那么消息头中的目标客户端ID就应该设置为B的ID(当然,首先是发送给中央服务器,然后再由中央服务器转发);相反如果消息就是发送给中央服务器的,那么消息头中的目标客户端ID就应该设置为0。任何客户端的ID,不论是静态分配好的还是由中央服务器动态分配的,都不应为0。
2.3.2 XML消息和二进制消息的格式框架
确定了消息头的内容后,我们就可以定义XML业务消息的格式如下:
<msg> <Header> <type>消息类型:request|ack|notify|broadcast|local</type> <source>源客户端ID或者-1</source> <dest>目的客户端ID或者0</dest> <sn>消息序列号</sn> <id>消息ID</id> </Header> <Body> 业务消息的消息体内容,由用户自己定义 </Body> </msg> |
定义二进制消息的格式如下图所示:
图2-3 二进制业务消息格式
分别对应的半结构化消息类型定义如下:
struct XMLHalfStructuredMessage { MessageHeaderSmartPtr m_msgHeader; // 消息头结构 XMLMessageBody m_msgBody; // 消息体仍然是DOM树结构 }; #ifdef _USE_XML_MESSAGE_FORMAT_ typedef SmartPtr<XMLHalfStructuredMessage> HalfStructuredMessageSmartPtr; #endif // _USE_XML_MESSAGE_FORMAT_
typedef STD::vector<BYTE> BinMessageBody; struct BinHalfStructuredMessage { MessageHeaderSmartPtr m_msgHeader; // 消息头结构 BinMessageBody m_msgBody; // 消息体保持二进制字节流 }; #ifdef _USE_BINARY_MESSAGE_FORMAT_ typedef SmartPtr<BinHalfStructuredMessage> HalfStructuredMessageSmartPtr; #endif // _USE_BINARY_MESSAGE_FORMAT_ |
显然,在一个系统中只能选择其中之一。
XMLMessageBody是对DOMDocument的一个封装,具体实现请参考附录指出的代码(HYQ_XML_message_body.h)。
当需要在客户端之间传递消息时,以中央服务器作为转发中心并配合这样的消息格式设计,就可以大大简化它们之间的连接关系,也可以简化为完成消息发送所需做的工作。启用消息转发的前提是必须同时在客户端和中央服务器上启用系统路由表功能,以及实现路由同步机制。关于路由表实现,请参考本章2.9节。