類CNCClient客戶機(jī)用來實(shí)現(xiàn)“發(fā)布—訂閱”和“組播”功能,主要接口(公有函數(shù))如下:
class CNCClient
{ public:
BOOL Connect(…); // 連接服務(wù)器
BOOL Disconnect(); // 與服務(wù)器斷開連接
BOOL PublishData(…); // 向服務(wù)器發(fā)布數(shù)據(jù)
BOOL QueryData(…); // 向服務(wù)器查詢數(shù)據(jù)
BOOL SubscribeData(…); // 向服務(wù)器訂閱數(shù)據(jù)
GROUPIP QueryGroupIP(…); // 向服務(wù)器查詢組播地址
DWORD MulticastMessage(…); // 發(fā)送組播消息
virtual void MessageResponse(…);// 響應(yīng)組播消息
…
};
一、客戶程序的“發(fā)布”協(xié)議
客戶機(jī)向服務(wù)器發(fā)布的每個(gè)數(shù)據(jù)報(bào)均含有數(shù)據(jù)類型、工作組名稱、數(shù)據(jù)名稱、生命期和數(shù)據(jù)長度的信息。報(bào)文格式如圖5.16所示,數(shù)據(jù)結(jié)構(gòu)見 DataPublish :
struct DataPublish
{
BYTE iDataType; // 2 個(gè)字節(jié)數(shù)據(jù)類型,宏定義為DATA_PUBLISH
char strGroupName[16]; // 16個(gè)字節(jié)的工作組名字
char strDataName[16]; // 16個(gè)字節(jié)的數(shù)據(jù)名字
DWORD dwLifeTime; // 4 個(gè)字節(jié)的數(shù)據(jù)生命期,以秒為單位
DWORD dwLength; // 4 個(gè)字節(jié)的數(shù)據(jù)內(nèi)容的長度
char *pchContent; // 數(shù)據(jù)內(nèi)容
};
data type group name data name life time length content
圖5.16 用于發(fā)布的報(bào)文格式
二、客戶程序的“訂閱”協(xié)議
客戶機(jī)向服務(wù)器訂閱數(shù)據(jù)分兩步實(shí)現(xiàn):
(1)先調(diào)用函數(shù)QueryData向服務(wù)器發(fā)送一個(gè) DataQuery格式的報(bào)文,用于查詢要訂閱的數(shù)據(jù)是否存在。
struct DataQuery
{
BYTE iDataType; // 2 個(gè)字節(jié)數(shù)據(jù)類型,宏定義為DATA_QUERY
char strGroupName[16]; // 16個(gè)字節(jié)的工作組名字
char strDataName[16]; // 16個(gè)字節(jié)的數(shù)據(jù)名字
};
(2)服務(wù)器接收到查詢時(shí),按照 DataQuery結(jié)構(gòu)中的strGroupName和strDataName進(jìn)行搜索。如果該數(shù)據(jù)不存在,Server向Client發(fā)送一個(gè)FALSE標(biāo)志。如果該數(shù)據(jù)存在,服務(wù)器先向客戶機(jī)發(fā)送一個(gè)TRUE標(biāo)志,之后立即再向客戶機(jī)發(fā)送該數(shù)據(jù)(DataPublish格式)。
如果客戶機(jī)得到TRUE標(biāo)志的查詢結(jié)果,就調(diào)用函數(shù)SubscribeData來接收服務(wù)器發(fā)送過來的數(shù)據(jù)。
三、客戶程序的“組播”協(xié)議
客戶機(jī)先調(diào)用函數(shù)QueryGroupIP向服務(wù)器發(fā)送一個(gè)GroupAddress格式的報(bào)文,用于查詢組播地址。服務(wù)器返回相應(yīng)的十進(jìn)制點(diǎn)分式的IP地址。
struct GroupAddress
{
BYTE iDataType; // 2 個(gè)字節(jié)數(shù)據(jù)類型,宏定義為GROUP_ADDRESS
char strGroupName[16]; // 16個(gè)字節(jié)的工作組名字
};
客戶機(jī)調(diào)用函數(shù)MulticastMessage向指定的組(根據(jù)組播地址)播放消息。組播的數(shù)據(jù)報(bào)結(jié)構(gòu) DataMulticast定義如下:
struct DataMulticast
{
DWORD dwContentType; // 組播的數(shù)據(jù)報(bào)類型,由用戶定義
char *pchContent; // 組播的數(shù)據(jù)報(bào)內(nèi)容,由用戶定義
};
如果客戶機(jī)接收到組播的消息,將自動調(diào)用函數(shù)MessageResponse來響應(yīng)該消息。MessageResponse是虛函數(shù),它將根據(jù)dwContentType信息決定如何處理到來的組播消息,具體功能由用戶定義。
5.5.3.2 CNC 服務(wù)器的設(shè)計(jì)
一、數(shù)據(jù)結(jié)構(gòu)
CNC 服務(wù)器的數(shù)據(jù)結(jié)構(gòu)主要由三部分組成:
(1)一張用于管理組播地址的鏈表。組播地址由服務(wù)器動態(tài)生成,客戶機(jī)可以向服務(wù)器查詢?nèi)我饨M的組播地址。
(2)一張用于管理線程指針的鏈表。服務(wù)器采用多線程并發(fā)處理技術(shù),使客戶機(jī)獲得最快的響應(yīng)。
(3)每個(gè)組都有一張用于管理“發(fā)布—訂閱”的數(shù)據(jù)的bbbb表。由于同一時(shí)刻,系統(tǒng)可能存在多個(gè)生產(chǎn)者與消費(fèi)者,數(shù)據(jù)的存入、取出速度成為服務(wù)器性能的重要指標(biāo)。bbbb表可以提供比鏈表更快的數(shù)據(jù)檢索速度。bbbb表中的數(shù)據(jù)項(xiàng)結(jié)構(gòu)見DataElement :
struct DataElement
{
char strGroupName[16]; // 工作組的名稱
char strDataName[16]; // 數(shù)據(jù)的名稱
BYTE iStorageType; // 存儲類型: STORAGE_FILE 或 STORAGE_MEMORY
ColeDateTime TimeToDie; // 作廢時(shí)刻
BOOL bLock; // 鎖定標(biāo)志: TRUE 或 FALSE
DWORD dwLength; // 數(shù)據(jù)的長度
char *pchContent; // 數(shù)據(jù)內(nèi)容
};
存儲類型(iStorageType)的用途:把數(shù)據(jù)全部保存在內(nèi)存中將非常消耗服務(wù)器的內(nèi)存資源,在很多情況下是沒有必要的。為了提高內(nèi)存的使用效率,服務(wù)器僅把生命期較短或者長度較短的數(shù)據(jù)保存在內(nèi)存中(即為STORAGE_MEMORY類型),而把生命期較長或者長度較長的數(shù)據(jù)保存在文件中(即為STORAGE_FILE類型)。
作廢時(shí)刻(TimeToDie)的用途:客戶機(jī)發(fā)布的數(shù)據(jù)均指定了生命期,服務(wù)器在接收到數(shù)據(jù)時(shí)即可計(jì)算出作廢時(shí)刻。服務(wù)器將定期掃描bbbb表,若發(fā)現(xiàn)有數(shù)據(jù)超出作廢時(shí)刻(并且沒有被鎖定),即可刪除此數(shù)據(jù)。
鎖定標(biāo)志(bLock)的用途:很多客戶機(jī)可能同時(shí)訂閱某個(gè)數(shù)據(jù),而該數(shù)據(jù)可能已超出作廢時(shí)刻即將被刪除。為避免沖突,規(guī)定只要有客戶機(jī)訂閱數(shù)據(jù),就用iLock標(biāo)志來鎖定此數(shù)據(jù),直到訂閱完成后才消除鎖定。
二、多線程并發(fā)技術(shù)
服務(wù)器有一個(gè)主線程和多個(gè)子線程。主線程負(fù)責(zé)客戶機(jī)的入連接請求,然后創(chuàng)建一個(gè)子線程來處理這個(gè)TCP連接。每個(gè)子線程按照CNC API的協(xié)議與客戶機(jī)通訊。由于有多個(gè)子線程共享服務(wù)器中的數(shù)據(jù),多線程對共享資源的同步訪問成為實(shí)現(xiàn)的難點(diǎn)。CNC 主要采用了關(guān)鍵區(qū)、互斥對象等同步手段解決這個(gè)問題。
三、Winsock的使用
CNC 1.0運(yùn)行于bbbbbbs 9x/NT系統(tǒng)下,底層的網(wǎng)絡(luò)通訊程序用Winsock編寫。Winsock有兩種工作方式:阻塞方式和非阻塞方式。阻塞方式的優(yōu)點(diǎn)是編程簡單,可靠性好。缺點(diǎn)是容易使應(yīng)用程序阻塞住,不能處理其它事務(wù)。非阻塞方式是利用bbbbbbs 消息機(jī)制實(shí)現(xiàn)的。優(yōu)點(diǎn)是在數(shù)據(jù)到來的時(shí)候,系統(tǒng)向應(yīng)用程序窗口發(fā)送消息,使得應(yīng)用程序不必總在等待數(shù)據(jù),提高了工作效率。缺點(diǎn)是在發(fā)送和接收數(shù)據(jù)時(shí),應(yīng)用程序并不將事情做完(不阻塞),以至于應(yīng)用程序要維護(hù)復(fù)雜的狀態(tài)機(jī)。
鑒于阻塞方式和非阻塞方式各有優(yōu)缺點(diǎn),CNC 服務(wù)器采用了混合方式。主線程采用非阻塞的消息驅(qū)動方式,可以快速響應(yīng)客戶機(jī)的入連接。在子線程中,仍采用非阻塞的消息驅(qū)動方式接受客戶機(jī)的請求,只有在響應(yīng)請求時(shí),采用阻塞的方式一次性地完成數(shù)據(jù)的發(fā)送或接收。
5.5.4 應(yīng)用示例
圖5.17、圖5.18是參加協(xié)同工作的兩個(gè)客戶程序示例,這兩個(gè)程序均用Intra3D 2.0 和CNC 1.0開發(fā)。圖5.17的客戶程序向CNC 服務(wù)器訂閱 .3ds和 .obj格式的多邊形模型數(shù)據(jù)并執(zhí)行交互式繪制。圖5.18的客戶程序向CNC 服務(wù)器訂閱商業(yè)統(tǒng)計(jì)圖形數(shù)據(jù)并執(zhí)行交互式繪制。另有一個(gè)客戶機(jī)(數(shù)據(jù)源)向CNC 服務(wù)器發(fā)布各種數(shù)據(jù),并用組播來通知各個(gè)客戶機(jī)當(dāng)前發(fā)布了什么數(shù)據(jù)(短消息)。
5.6 小 結(jié)
讓我們用著名3D游戲軟件Quake的設(shè)計(jì)師Michael Abrash 的話總結(jié)本章:“所有真正杰出的設(shè)計(jì)一旦被設(shè)計(jì)好,看起來都是那么的簡單和顯而易見。但是在獲得杰出設(shè)計(jì)的過程中,需要付出令人難以置信的努力?!?/SPAN>[Abrash 1998]
圖5.17 繪制.3ds和.obj模型的客戶程序
圖5.18 繪制商業(yè)統(tǒng)計(jì)圖形的客戶程序










