了解網(wǎng)絡(luò)互聯(lián)
網(wǎng)絡(luò)是指多臺計算機(jī)互聯(lián)以進(jìn)行數(shù)據(jù)傳輸及通信的系統(tǒng)。除了兩個或更多的計算機(jī)之外,網(wǎng)路還需要有網(wǎng)絡(luò)互聯(lián)軟件(或一個網(wǎng)路操作系統(tǒng))、網(wǎng)絡(luò)適配器以及電纜。網(wǎng)絡(luò)適配器有各種形狀和大小,但是一般都采用調(diào)制解調(diào)器的形狀。實際上,調(diào)制解調(diào)器就是一個網(wǎng)路適配器,它能夠?qū)⒁慌_計算機(jī)通過世界上最大的網(wǎng)絡(luò)--
-互聯(lián)網(wǎng)連接到數(shù)百萬臺計算機(jī)上。
網(wǎng)路模型
網(wǎng)絡(luò)互聯(lián)模型有三種基本類型:服務(wù)器端、客戶端以及點對點。
使用服務(wù)器端模型,可以建立一個中央網(wǎng)絡(luò)互聯(lián)系統(tǒng)。其他計算機(jī)使用客戶端模型連接到服務(wù)器端后,就可以向服務(wù)器端發(fā)送數(shù)據(jù)以及從服務(wù)器端接收數(shù)據(jù)。客戶端沒有其他客戶端的信息,不直接與它們連接,客戶端都只知道服務(wù)器端的信息,而服務(wù)器端則擁有所有客戶端的信息以及適合這些客戶端之間的路由信息。服務(wù)器端和客戶端常常成對進(jìn)行描述,即服務(wù)器端/客戶端(C/S)模型,但是在使用DirectPlay時,將二者分開是有必要的,因為服務(wù)器端和客戶端是由兩個獨立的組件組成的。
下圖演示了服務(wù)器端和客戶端模型之間的關(guān)系:

點對點(peer-to-peer)這種網(wǎng)絡(luò)互聯(lián)模型與服務(wù)器端模型或客戶端模型正好相反,計算機(jī)相互之間直接進(jìn)行連接。每一臺新的計算機(jī)加入網(wǎng)絡(luò)會話中,都會建立一個新的連接,所以每臺計算機(jī)都能直接連接到其他計算機(jī)。連接到網(wǎng)絡(luò)的時段稱為會話,一次會話會有與之相關(guān)的屬性,如密碼、最大連接數(shù)等。
如下圖所示,一個4臺計算機(jī)的網(wǎng)絡(luò)擁有12個連接(每臺計算機(jī)與其他三臺計算機(jī)之間都有一個連接)。

游戲廳
可以將游戲廳服務(wù)器看作在線玩家的會議大廳,一個游戲廳允許所有玩家登錄、通信以及加入他們喜愛的一些游戲。一旦游戲廳服務(wù)器連滿了玩家,就停止對游戲廳的循環(huán)(這樣做是為了節(jié)省網(wǎng)絡(luò)帶寬)。網(wǎng)絡(luò)帶寬(network
bandwidth)指的是一個網(wǎng)絡(luò)連接能夠輕松處理的數(shù)據(jù)量,高網(wǎng)絡(luò)帶寬連接能比低網(wǎng)路帶寬連接更快地處理大量的網(wǎng)絡(luò)數(shù)據(jù)。
響應(yīng)時間和延遲
帶寬引出了兩個術(shù)語:響應(yīng)時間和延遲。響應(yīng)時間是完成一個操作所花的時間(越低越好)的量化。延遲是用來描述網(wǎng)絡(luò)通信的遲滯的術(shù)語,即數(shù)據(jù)從發(fā)送到它被接收到所花的時間。
低延遲表示網(wǎng)絡(luò)數(shù)據(jù)迅速地被接收。高延遲(最不希望出現(xiàn)的事情)表示網(wǎng)絡(luò)數(shù)據(jù)被延遲或者根本沒有被發(fā)送到。延遲是一個主要問題,特別是使用了互聯(lián)網(wǎng)時,就必須處理這個問題。
通信協(xié)議
網(wǎng)絡(luò)可以有各種方式進(jìn)行相互通信,但是要連接到另一個,兩個系統(tǒng)都必須采用相同的協(xié)議。目前最流行的協(xié)議是TCP/IP協(xié)議(傳輸控制協(xié)議
/Internet協(xié)議),它被廣泛應(yīng)用于互聯(lián)網(wǎng)。通信協(xié)議也被稱作服務(wù)器提供者,無論服務(wù)器提供者是一種諸如IPX、TCP/IP的協(xié)議,還是一種諸如調(diào)制解調(diào)器或者串行電纜的設(shè)備,都可以把它看作是網(wǎng)絡(luò)互聯(lián)的連接體。
TCP/IP協(xié)議是一種在網(wǎng)絡(luò)上傳輸數(shù)據(jù)包的方法。它將數(shù)據(jù)分割成很多小數(shù)據(jù)包,再加上發(fā)送方和接收方的地址以及包的數(shù)目,便于重組數(shù)據(jù)。
如下圖所示:

信息在傳輸過程中丟失(頻繁發(fā)生的事情),TCP/IP允許網(wǎng)絡(luò)重發(fā)數(shù)據(jù)包。當(dāng)出現(xiàn)延遲時,這些數(shù)據(jù)包可能以錯誤的順序被接收,比如舊的數(shù)據(jù)包在新的數(shù)據(jù)包之后到達(dá)。好在無需擔(dān)心,因為TCP/IP會重發(fā)丟失的包并且重組無序的包。
尋址
在TCP/IP協(xié)議下,一個系統(tǒng)被分配一個由4個數(shù)字(0 -255之間)組成的網(wǎng)絡(luò)地址(IP地址),數(shù)字之間用點分割。一個IP地址就像下面這樣:
64.120.53.2
IP地址對我們來說不好辨認(rèn),但是網(wǎng)絡(luò)卻可以根據(jù)每一個數(shù)值成功地傳遞數(shù)據(jù)。稍加運算就會發(fā)現(xiàn),這4個數(shù)字的組合總共可以提供4294967296個可能的地址。為了增加地址數(shù)量,網(wǎng)絡(luò)使用附加的稱為端口的地址值,數(shù)據(jù)被傳送到其上。
如果將IP地址比喻成一個郵政室(mailroom),那么該郵政室(IP地址)描述了與網(wǎng)絡(luò)相連的單個計算機(jī)系統(tǒng),并且該計算機(jī)系統(tǒng)只被分配了惟一的IP
地址。在郵政室中有很多箱柜(端口),郵件被分類到其中。每一個箱柜(端口)屬于一個特定的工作人員(一個特定的應(yīng)用程序)。一些應(yīng)用程序可以擁有多個端口,數(shù)據(jù)只能被一個同相應(yīng)IP地址以及相應(yīng)端口所匹配的系統(tǒng)接收。一種稱為數(shù)據(jù)路由器(data
router)的設(shè)備將接收到的網(wǎng)絡(luò)數(shù)據(jù)傳送到它所知道的系統(tǒng),或者將數(shù)據(jù)傳送到另一個網(wǎng)絡(luò)連接。
下圖演示了路由的過程,說明網(wǎng)絡(luò)數(shù)據(jù)可以通過數(shù)據(jù)路由器進(jìn)行傳遞。

DirectPlay概述
要在工程中使用DirectPlay,必須包含DPlay8.h和DPAddr.h,還要鏈接DPlayX.lib和 DXGuid.lib。
網(wǎng)絡(luò)對象
使用DirectPlay,需要使用前面提到的網(wǎng)絡(luò)模型:客戶端、服務(wù)器端和點對點。每一種網(wǎng)絡(luò)模型都有自己的接口對象,如下所示:
IDirectPlay8Client:客戶端網(wǎng)絡(luò)對象,連接到一個服務(wù)器端。
IDirectPlay8Server:服務(wù)器端網(wǎng)絡(luò)對象,連接多個客戶端。
IDirectPlay8Peer: 點對點網(wǎng)絡(luò)對象,連接客戶端與其他客戶端。
IDirectPlay8Address:包含(以及構(gòu)造)網(wǎng)絡(luò)地址的對象。
來看看IDirectPlay8Client的簡要說明:
Applications use the methods of the IDirectPlay8Client interface to
create and manage client applications for client/server sessions.
The methods of the IDirectPlay8Client interface can be organized into
the following groups.
Session
Management |
Close |
|
Connect |
|
EnumHosts |
|
EnumServiceProviders |
|
GetApplicationDesc |
|
GetCaps |
|
GetSPCaps |
|
SetCaps |
|
SetSPCaps |
Message
Management |
GetSendQueueInfo |
|
Initialize |
|
ReturnBuffer |
|
Send |
Client
Information |
SetClientInfo |
Server
Information |
GetServerInfo |
Miscellaneous |
CancelAsyncOperation |
|
RegisterLobby |
|
GetConnectionInfo |
|
GetServerAddress |
來看看IDirectPlay8Server的簡要說明:
Applications use the methods of the IDirectPlay8Server interface to
create and manage the server for a Microsoft® DirectPlay® client/server
transport session
The methods of the IDirectPlay8Server interface can be organized into
the following groups.
Session
Management |
Close |
|
EnumServiceProviders |
|
GetApplicationDesc |
|
GetCaps |
|
GetSPCaps |
|
GetSendQueueInfo |
|
Host |
|
Initialize |
|
ReturnBuffer |
|
SendTo |
|
SetApplicationDesc |
|
SetCaps |
|
SetServerInfo |
|
SetSPCaps |
Client
Management |
DestroyClient |
|
GetClientInfo |
|
GetPlayerContext |
Group
Management |
AddPlayerToGroup |
|
CreateGroup |
|
DestroyGroup |
|
EnumPlayersAndGroups |
|
EnumGroupMembers |
|
GetGroupContext |
|
GetGroupInfo |
|
RemovePlayerFromGroup |
|
SetGroupInfo |
Miscellaneous |
CancelAsyncOperation |
|
GetClientAddress |
|
GetConnectionInfo |
|
GetLocalHostAddresses |
|
RegisterLobby |
要連接到遠(yuǎn)程網(wǎng)絡(luò)系統(tǒng)(或主持一次會話),需要使用
IDirectPlay8Address構(gòu)造一個網(wǎng)絡(luò)地址,IDirectPlay8Address的惟一用途就是構(gòu)造并包含單個網(wǎng)絡(luò)地址。會話指的是主持或加入一個可連接網(wǎng)路系統(tǒng)的時段,終止連接時,會話也就終止了。要主持游戲會話或連接到遠(yuǎn)程系統(tǒng),首先需要創(chuàng)建網(wǎng)絡(luò)對象并給它分配地址。要主持游戲會話,只需要等待其他系統(tǒng)(也就是使用這些系統(tǒng)的人)連接上來即可。連接建立之后,系統(tǒng)就可以和遠(yuǎn)程系統(tǒng)開始相互傳輸游戲相關(guān)的網(wǎng)絡(luò)消息,
DirectPlay把這些遠(yuǎn)程系統(tǒng)稱作玩家。
將玩家進(jìn)行分組
在DirectPlay
術(shù)語中,“玩家”是指通過網(wǎng)絡(luò)連接到其他計算機(jī)的單個連接(通常是一個游戲玩家)。一臺計算機(jī)可以有多個玩家,但一般只有一個。實際上,服務(wù)器端也被標(biāo)識為玩家,以便于識別。每一個玩家都會接收到一個標(biāo)識號碼(玩家ID),系統(tǒng)使用這個標(biāo)識號碼來將消息傳遞到每個玩家。這些號碼是追蹤玩家的惟一可信任方法,所以需要讓程序?qū)λ鼈冞M(jìn)行相應(yīng)地處理。
對于大型游戲,可能有成千上萬已連接的玩家。為了在游戲中更好地處理這些玩家,可以將一些或所有玩家編制到一些組中。采用組的概念可以減少編程的繁瑣,主要原因在于可以將一個游戲區(qū)域(比如一張地圖或某個級別)中的多個玩家劃分為一組,并且一次向整個組發(fā)送網(wǎng)絡(luò)數(shù)據(jù),而不是單獨發(fā)送給各個玩家。使用組還有很多其他原因,但這個原因是最主要的。一個組中包含多少玩家以及創(chuàng)建多少個組完全沒有限制,組也可以屬于其他組。
如下圖所示:

帶消息的網(wǎng)絡(luò)互聯(lián)
消息是分了類的數(shù)據(jù)包,其中包含簡單的結(jié)構(gòu)。每一個消息都有特定的含義,且都有一個與之對應(yīng)的宏,而且還和所使用的網(wǎng)絡(luò)模型有關(guān)。要接收消息,網(wǎng)絡(luò)對象必須給自己指定一個回調(diào)函數(shù),以便于每次消息到達(dá)時進(jìn)行調(diào)用。為了確保得到平滑的數(shù)據(jù)流,該函數(shù)根據(jù)數(shù)據(jù)類型進(jìn)行分析并盡快地返回。發(fā)送消息,需要使用各個網(wǎng)絡(luò)對象的發(fā)送函數(shù)。這些函數(shù)易于使用,并且提供了許多發(fā)送選項,包括保證發(fā)送(guaranteed
delivery)、安全編碼(secure encryption)以及同步或異步發(fā)送。
同步與異步
DirectPlay提供的第一個發(fā)送選項是同步或異步發(fā)送消息的功能,也就是說系統(tǒng)在發(fā)出發(fā)送數(shù)據(jù)指定之后交回控制權(quán)(異步),還是直到所有的數(shù)據(jù)都成功發(fā)送之后再交回控制權(quán)(同步)。很多時候使用異步方法,因為它不會像同步方法那樣阻塞系統(tǒng)。
安全性
要引起注意的是任何時候都可能有人在截取并記錄游戲的網(wǎng)絡(luò)數(shù)據(jù)。因此,就需要采用一種安全的方式編碼消息數(shù)據(jù),使得那些解碼的黑客非常難讀懂先前的信息。使用安全網(wǎng)絡(luò)發(fā)送的不好之處在于它會稍稍減慢系統(tǒng)速度,因為必須在數(shù)據(jù)發(fā)送之前先編碼信息,然后在接收到之后進(jìn)行解碼。
保證發(fā)送
就像一些快遞公司保證送到包裹一樣,
DirectPlay也能保證送到消息。可以將一組信息標(biāo)識為保證的,其余確定的就是DirectPlay將一直執(zhí)行發(fā)送操作直到發(fā)送成功,以保證將其發(fā)送到目標(biāo)地址(除非斷線),使用保證發(fā)送是通過在調(diào)用函數(shù)時指定一個惟一的標(biāo)志來實現(xiàn)的。保證發(fā)送的不好之處在于速度,保證發(fā)送在實際的游戲狀況下非常慢,游戲采用UDP(用戶數(shù)據(jù)報協(xié)議)發(fā)送方法,就不用關(guān)心數(shù)據(jù)是否被接收到(和TCP發(fā)送方法相反,它保證數(shù)據(jù)的發(fā)送)。
節(jié)流
有時系統(tǒng)會因為試圖處理流數(shù)據(jù)而過載,盡管這樣, DirectPlay有一個內(nèi)置的消息節(jié)流器,它丟棄了發(fā)送隊列中低優(yōu)先級的信息。
下圖有助于更形象地理解節(jié)流機(jī)制的概念,想象一隊人在鎮(zhèn)上熱鬧的夜總會前等待,如果每個顧客表示一條消息,那么當(dāng)非常忙的時候,保鏢(節(jié)流機(jī)制)就必須拒絕那些隊列中較不重要的顧客。

使用GUID識別應(yīng)用程序
如何從眾多的網(wǎng)絡(luò)應(yīng)用程序中區(qū)分出自己的網(wǎng)絡(luò)應(yīng)用程序呢?解決這個問題的方法就是給自己的應(yīng)用程序指定一個惟一的號碼,并且只允許使用相同號碼的應(yīng)用程序相互進(jìn)行連接。這個特殊的號碼就是
Windows程序員熟悉的GUID(全局惟一標(biāo)識符)。創(chuàng)建應(yīng)用程序之前,花一點時間給它設(shè)置一個惟一的GUID,并且保證所有通過網(wǎng)絡(luò)進(jìn)行連接的該應(yīng)用程序使用相同的GUID。
初始化網(wǎng)絡(luò)對象
無論是服務(wù)器端、客戶端還是單點對象,使用DirectPlay的第一步都是創(chuàng)建網(wǎng)絡(luò)對象。要初始化每一個網(wǎng)絡(luò)模型接口,必須使用CoCreateInstance函數(shù),可能用到的類ID和引用標(biāo)識符如下所示:
CLSID_DirectPlay8Address |
IID_IDirectPlay8Address |
CLSID_DirectPlay8Client |
IID_IDirectPlay8Client |
CLSID_DirectPlay8Peer |
IID_IDirectPlay8Peer |
CLSID_DirectPlay8Server |
IID_IDirectPlay8Server |
無論創(chuàng)建的是什么網(wǎng)絡(luò)對象(客戶端、單點或服務(wù)器端),都需要創(chuàng)建與之匹配的網(wǎng)絡(luò)回調(diào)函數(shù),在網(wǎng)絡(luò)消息被接收到的時候會調(diào)用該回調(diào)函數(shù)。
該回調(diào)函數(shù)使用說明如下:
PFNDPNMESSAGEHANDLER is an application-defined callback
function used by the IDirectPlay8Peer,
IDirectPlay8Client, and IDirectPlay8Server
IDirectPlay8LobbyClient and IDirectPlay8LobbiedApplication
interfaces to process messages.
typedef HRESULT (WINAPI *PFNDPNMESSAGEHANDLER)(
PVOID pvUserContext,
DWORD dwMessageType,
PVOID pMessage
);
Parameters
- pvUserContext
- Pointer to the application-defined structure that will be passed to this
callback function. This is defined in the pvUserContext parameter
of the Initialize method.
- dwMessageType
- One of the following message types that are generated by the
IDirectPlay8Peer, IDirectPlay8Client, and
IDirectPlay8Server interfaces. Each interface uses a different
subset of the available messages. Refer to the interface documentation for
details.
- DPN_MSGID_ADD_PLAYER_TO_GROUP
- DPN_MSGID_ASYNC_OP_COMPLETE
- DPN_MSGID_CLIENT_INFO
- DPN_MSGID_CONNECT_COMPLETE
- DPN_MSGID_CREATE_GROUP
- DPN_MSGID_CREATE_PLAYER
- DPN_MSGID_DESTROY_GROUP
- DPN_MSGID_DESTROY_PLAYER
- DPN_MSGID_ENUM_HOSTS_QUERY
- DPN_MSGID_ENUM_HOSTS_RESPONSE
- DPN_MSGID_GROUP_INFO
- DPN_MSGID_HOST_MIGRATE
- DPN_MSGID_INDICATE_CONNECT
- DPN_MSGID_INDICATED_CONNECT_ABORTED
- DPN_MSGID_PEER_INFO
- DPN_MSGID_RECEIVE
- DPN_MSGID_REMOVE_PLAYER_FROM_GROUP
- DPN_MSGID_RETURN_BUFFER
- DPN_MSGID_SEND_COMPLETE
- DPN_MSGID_SERVER_INFO
- DPN_MSGID_TERMINATE_SESSION
Additionally, if the application supports Microsoft® DirectPlay® lobby
functionality, this parameter can specify one of the following message types
that are generated by the IDirectPlay8LobbyClient and
IDirectPlay8LobbiedApplication interfaces. Each interface
uses a different subset of the available messages. Refer to the interface
documentation for details.
- DPL_MSGID_CONNECT
- DPL_MSGID_CONNECTION_SETTINGS
- DPL_MSGID_DISCONNECT
- DPL_MSGID_RECEIVE
- DPL_MSGID_SESSION_STATUS
- pMessage
- Structure containing message information.
Return Values
See the documentation for the individual messages for appropriate return
values. Unless otherwise noted, this function should return S_OK.
Remarks
This function must be threadsafe because it might be called reentrantly
through multiple threads.
Callback messages from the same player are serialized. Once you receive a
message from a player, you will not receive another until you have handled the
first message, and the callback function has returned.
The message structures have the same name as the message type except the
"DPN_MSGID" is replaces with "DPNMSG". For example, the
DPN_MSGID_CONNECTION_TERMINATED message type uses the
DPNMSG_CONNECTION_TERMINATED message structure to convey the actual
message information.
When implementing this callback function, first look at the message type
returned in the dwMessageType parameter and then cast the message
structure (pMessage) to that type to obtain message information. Some
messages don't have a defined structure because they have no parameters. For
these messages, the pMessage parameter is NULL.
要初始化網(wǎng)絡(luò)對象,需要調(diào)用Initialize函數(shù)。
Registers an entry point in the client's code that receives the messages from
the IDirectPlay8Client interface and from the server. This
method must be called before calling any other methods of this interface.
HRESULT Initialize(
PVOID const pvUserContext,
const PFNDPNMESSAGEHANDLER pfn,
const DWORD dwFlags
);
Parameters
- pvUserContext
- [in] Pointer to the user-provided context value in calls to the message
handler. Providing a user-context value can be useful to differentiate
messages coming from multiple interfaces to a common message handler.
- pfn
- [in] Pointer to a PFNDPNMESSAGEHANDLER callback
function that receives all messages from the server, and receives
indications of session changes from the IDirectPlay8Client
interface.
- dwFlags
- [in] You may specify the following flag.
- DPNINITIALIZE_DISABLEPARAMVAL
- Disable parameter validation for the current object.
Return Values
Returns S_OK if successful, or one of the following error values.
DPNERR_INVALIDFLAGS |
DPNERR_INVALIDPARAM |
Remarks
This is the first method you should call after using CoCreateInstance
to obtain the IDirectPlay8Client interface.
下面這個函數(shù)創(chuàng)建了DirectPlay Client對象并且初始化該對象。
//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
// create DirectPlay client component
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client,
(void**)&g_dp_client)))
return FALSE;
// Assign a message handler to network component
//
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
// interface and from the server.
if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
return FALSE;
return TRUE;
}
指定設(shè)備
雖然已經(jīng)選擇了一個服務(wù)提供者,但系統(tǒng)中可能有一個以上的設(shè)備使用它。當(dāng)一個網(wǎng)絡(luò)適配器和調(diào)制解調(diào)器都連接到互聯(lián)網(wǎng),并且都使用TCP/IP協(xié)議時,就會出現(xiàn)這種情況,這是就必須對設(shè)備進(jìn)行枚舉并進(jìn)行選擇。
枚舉函數(shù)獲取了所有可用的服務(wù)提供者,并將它們以可用的方式組織在一起。DirectPlay枚舉和典型的
Windwos枚舉方法有所不同,在查詢過程中找到實例對象并不是每次都調(diào)用回調(diào)函數(shù),而是從包含了以數(shù)組形式存儲的每個服務(wù)提供者的緩沖區(qū)中尋找,如下圖所示:

處理枚舉可以使用EnumServiceProviders函數(shù)。
Enumerates the registered service providers available to the application.
HRESULT EnumServiceProviders(
const GUID *const pguidServiceProvider,
const GUID *const pguidApplication,
DPN_SERVICE_PROVIDER_INFO *const pSPInfoBuffer,
PDWORD const pcbEnumData,
PDWORD const pcReturned,
const DWORD dwFlags
);
Parameters
- pguidServiceProvider
- [in] Pointer to a variable of type GUID that specifies
a service provider. This optional parameter forces the enumeration of
subdevices for the specified service provider. You should normally set this
value to NULL, to enumerate all available service providers.
- pguidApplication
- [in] Pointer to a variable of type GUID that specifies
an application. If a pointer is passed in this parameter, only service
providers who can be connected to the application are enumerated. You can
also pass NULL to enumerate the registered service providers for the system.
- pSPInfoBuffer
- [out] Pointer to an array of DPN_SERVICE_PROVIDER_INFO
structures that will be filled with service provider information.
- pcbEnumData
- [out] Pointer to DWORD, which is filled with the size
of the pSPInfoBuffer array, in bytes, if the buffer is too small.
- pcReturned
- [out] Pointer to a variable of type DWORD that
specifies the number of DPN_SERVICE_PROVIDER_INFO
structures returned in the pSPInfoBuffer array.
- dwFlags
- [in] The following flag can be specified.
- DPNENUMSERVICEPROVIDERS_ALL
- Enumerates all the registered service providers for the system,
including those that are not available to the application or do not have
devices installed.
Return Values
Returns S_OK if successful, or one of the following error values.
DPNERR_BUFFERTOOSMALL |
DPNERR_INVALIDPARAM |
Remarks
Call this method initially by specifying NULL in the pguidServiceProvider
parameter to determine the base service providers available to the system.
Specific devices for a service provider can be obtained by passing a pointer to
a service provider GUID in the pguidServiceProvider. This is useful,
for example, when using the Modem Connection for Microsoft® DirectPlay® service
provider. You can choose among different modems for dialing out and select
specific modems for hosting.
If the pEnumData buffer is not big enough to hold the requested
service provider information, the method returns DPNERR_BUFFERTOOSMALL and the
cbEnumData parameter contains the required buffer size. Typically, the
best strategy is to call the method once with a zero-length buffer to determine
the required size. Then call the method again with the appropriate-sized buffer.
Normally, this method will return only those service providers that can be
used by the application. For example, if the IPX networking protocol is not
installed, DirectPlay will not return the IPX service provider. To have
DirectPlay return all service providers, even those that cannot be used by the
application, set the DPNENUMSERVICEPROVIDERS_ALL flag in dwFlags.
pSPInfoBuffer是指向DPN_SERVICE_PROVIDER_INFO結(jié)構(gòu)體數(shù)組的指針,該函數(shù)將用枚舉的信息填充該結(jié)構(gòu)體。
Used when enumerating information for a specific service provider.
typedef struct _DPN_SERVICE_PROVIDER_INFO{
DWORD dwFlags;
GUID guid;
WCHAR* pwszName;
PVOID pvReserved;
DWORD dwReserved;
} DPN_SERVICE_PROVIDER_INFO, *PDPN_SERVICE_PROVIDER_INFO;
Members
- dwFlags
- Reserved. Must be 0.
- guid
- GUID for the service provider.
- pwszName
- Name of the service provider.
- pvReserved
- Reserved. Must be 0.
- dwReserved
- Reserved. Must be 0.
下面這個函數(shù)演示了如何枚舉系統(tǒng)中的服務(wù)提供者:
HWND g_hwnd; // window handles
IDirectPlay8Client* g_dp_client; // directplay client
// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO* g_sp_list;
DWORD g_sp_number; // service provider number
//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
// return is no server object
if(g_dp_client == NULL)
return;
// get a handler to the list box
HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);
// clear the list box
SendMessage(listbox, LB_RESETCONTENT, 0, 0);
// release service provider list memory
delete[] g_sp_list;
g_sp_list = NULL;
// query the required size of the data buffer
DWORD sp_list_size = 0;
// enumerates the registerd service providers available to the application
HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);
if(rv != DPNERR_BUFFERTOOSMALL)
return;
// allocate a buffer
if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[sp_list_size]) == NULL)
return;
// enumerate again
if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
{
// enumeration is complete, scan through entries.
char sp_name[1024];
DPN_SERVICE_PROVIDER_INFO* sp_ptr = g_sp_list;
for(DWORD i = 0; i < g_sp_number; i++)
{
// convert wide string into multi-byte string
wcstombs(sp_name, sp_ptr->pwszName, 1024);
// Add the service provider into box
//
// An application sends an LB_ADDSTRING message to add a string to a list box.
// If the list box does not have the LBS_SORT style, the string is added to the end of the list.
// Otherwise, the string is inserted into the list and the list is sorted.
SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);
// go to next service provider in buffer
sp_ptr++;
}
}
}
下面給出一個完整實例來運用剛才介紹的知識,
由于DirectX9
SDK已不包含DirectPlay,所以需要安裝DirectX8 SDK,并在Visual Studio中進(jìn)行相應(yīng)設(shè)置。
點擊下載源碼和工程
/***************************************************************************************
PURPOSE:
Enum Network Adapters Demo
***************************************************************************************/
#include <windows.h>
#include <dplay8.h>
#include <dpaddr.h>
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dplayx.lib")
#pragma warning(disable : 4996)
#define Safe_Release(p) if((p)) (p)->Release();
// window handles, class.
HWND g_hwnd;
char g_class_name[] = "EnumClass";
IDirectPlay8Client* g_dp_client; // directplay client
// service provider list, used when enumerating information for a specifice service provider.
DPN_SERVICE_PROVIDER_INFO* g_sp_list;
DWORD g_sp_number; // service provider number
//----------------------------------------------------------------------------------------
// callback function that receives all messages from the server, and receives indications
// of session changes from the IDirectPlay8Client interface.
//----------------------------------------------------------------------------------------
HRESULT WINAPI Net_Msg_Handle(PVOID user_context, DWORD message_id, PVOID msg_buffer)
{
// return S_OK to signify the message was handled OK.
return S_OK;
}
//--------------------------------------------------------------------------------
// Create DirectPlay Client and initialize it.
//--------------------------------------------------------------------------------
BOOL Init_DirectPlay_Client()
{
// create DirectPlay client component
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client,
(void**)&g_dp_client)))
return FALSE;
// Assign a message handler to network component
//
// Registers an entry point in the client's code that receives the messages from the IDirectPlay8Client
// interface and from the server.
if(FAILED(g_dp_client->Initialize(NULL, Net_Msg_Handle, 0)))
return FALSE;
return TRUE;
}
//--------------------------------------------------------------------------------
// Enumerate all available service provider.
//--------------------------------------------------------------------------------
void Enum_Service_Provider()
{
// return is no server object
if(g_dp_client == NULL)
return;
// get a handler to the list box
HWND listbox = GetDlgItem(g_hwnd, IDC_SERVICE_PROVIDERS);
// clear the list box
SendMessage(listbox, LB_RESETCONTENT, 0, 0);
// release service provider list memory
delete[] g_sp_list;
g_sp_list = NULL;
// query the required size of the data buffer
DWORD sp_list_size = 0;
// enumerates the registerd service providers available to the application
HRESULT rv = g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0);
if(rv != DPNERR_BUFFERTOOSMALL)
return;
// allocate a buffer
if((g_sp_list = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[sp_list_size]) == NULL)
return;
// enumerate again
if(SUCCEEDED(g_dp_client->EnumServiceProviders(NULL, NULL, g_sp_list, &sp_list_size, &g_sp_number, 0)))
{
// enumeration is complete, scan through entries.
char sp_name[1024];
DPN_SERVICE_PROVIDER_INFO* sp_ptr = g_sp_list;
for(DWORD i = 0; i < g_sp_number; i++)
{
// convert wide string into multi-byte string
wcstombs(sp_name, sp_ptr->pwszName, 1024);
// Add the service provider into box
//
// An application sends an LB_ADDSTRING message to add a string to a list box.
// If the list box does not have the LBS_SORT style, the string is added to the end of the list.
// Otherwise, the string is inserted into the list and the list is sorted.
SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)sp_name);
// go to next service provider in buffer
sp_ptr++;
}
}
}
//--------------------------------------------------------------------------------
// Enumerate all adapters which specified by sp_guid.
//--------------------------------------------------------------------------------
void Enum_Adapters(GUID* sp_guid)
{
// return if no server object or GUID
if(g_dp_client == NULL || sp_guid == NULL)
return;
// get a handle of the list box
HWND listbox = GetDlgItem(g_hwnd, IDC_ADAPTERS);
// clear the list box
SendMessage(listbox, LB_RESETCONTENT, 0, 0);
DPN_SERVICE_PROVIDER_INFO* adapter_list = NULL;
DWORD adapter_number = 0;
DWORD adapter_list_size = 0;
// query the required size of the data buffer
HRESULT rv = g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0);
if(rv != DPNERR_BUFFERTOOSMALL)
return;
// allocate a buffer
if((adapter_list = (DPN_SERVICE_PROVIDER_INFO*) new BYTE[adapter_list_size]) == NULL)
return;
// enumerate again
if(SUCCEEDED(g_dp_client->EnumServiceProviders(sp_guid, NULL, adapter_list, &adapter_list_size, &adapter_number, 0)))
{
char adapter_name[1024];
// enumeration is complete, scan through entries.
DPN_SERVICE_PROVIDER_INFO* adapter_ptr = adapter_list;
for(DWORD i = 0; i < adapter_number; i++)
{
// convert wide string into multi-byte string
wcstombs(adapter_name, adapter_ptr->pwszName, 1024);
// add the adapter name int listbox
SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM)adapter_name);
// go to next servicec provider
adapter_ptr++;
}
}
// delete the list memory resources
delete[] adapter_list;
}
//--------------------------------------------------------------------------------
// Release all resource which allocated for DirectPlay.
//--------------------------------------------------------------------------------
void Release_DirectPlay()
{
// release client service provider list memory
delete[] g_sp_list;
g_sp_list = NULL;
g_sp_number = 0;
// release client component
if(g_dp_client != NULL)
{
// Closes the open connection to a session. This method must be called on any object that is successfully
// initialized with a call to the IDirectPlay8Client::Initialize method.
g_dp_client->Close(0);
g_dp_client->Release();
g_dp_client = NULL;
}
}
//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_COMMAND:
// The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a
// notification message to its parent window, or when an accelerator keystroke is translated.
//
// wParam:
// The high-order word specifies the notification code if the message is from a control.
// If the message is from an accelerator, this value is 1. If the message is from a menu, this value is zero.
// The low-order word specifies the identifier of the menu item, control, or accelerator.
//
// lParam:
// Handle to the control sending the message if the message is from a control.
// Otherwise, this parameter is NULL.
// An application sends the LBN_SELCHANGE notification message when the selection in a list box is about to
// change. The parent window of the list box receives this notification message through the WM_COMMAND message.
if(LOWORD(wParam) == IDC_SERVICE_PROVIDERS && HIWORD(wParam) == LBN_SELCHANGE)
{
// Get the selection from the list
//
// Send an LB_GETCURSEL message to retrieve the index of the currently selected item,
// if any, in a single-selection list box.
unsigned int selected = (unsigned int) SendMessage(GetDlgItem(hwnd, IDC_SERVICE_PROVIDERS), LB_GETCURSEL, 0, 0);
// enumerate the adapters
DPN_SERVICE_PROVIDER_INFO* sp_ptr = g_sp_list;
sp_ptr += selected;
// enumerate adapter with specified guid
Enum_Adapters(&sp_ptr->guid);
}
return 0;
case WM_DESTROY:
Release_DirectPlay();
PostQuitMessage(0);
return 0;
}
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASS win_class;
MSG msg;
// create window class and register it
win_class.style = CS_HREDRAW | CS_VREDRAW;
win_class.lpfnWndProc = Window_Proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = DLGWINDOWEXTRA;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(inst, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
if(! RegisterClass(&win_class))
return FALSE;
// create the main window
g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_ENUM), 0, NULL);
ShowWindow(g_hwnd, cmd_show);
UpdateWindow(g_hwnd);
// initialize COM
//
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
// apartment (STA).
CoInitialize(0);
// Initialzie DirectPlay and enumerate service providers.
if(! Init_DirectPlay_Client())
{
MessageBox(NULL, "Error initializing DirectPlay.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
// enumerate all available service provider
Enum_Service_Provider();
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
UnregisterClass(g_class_name, inst);
// release COM system
//
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
// resources that the thread maintains, and forces all RPC connections on the thread to close.
CoUninitialize();
return (int) msg.wParam;
}
運行截圖: