?/TD> |
Microsoft DirectX 9.0 |
This tutorial shows you how to create a simple client/server session. Client/server sessions are often used for creating large-scale multiplayer games. One advantage for using a client/server game rather than a peer-to-peer game is that the majority of the processing can be done on a separate computer—the server—and therefore you do not need to rely on the power of the client's computer. For more information about client/server sessions, see Client/Server Topology and Client/Server Sessions.
Before beginning this tutorial, you should complete Tutorials 1 through 4, which describe how to create a peer-to-peer session. The steps for creating a client/server session are very similar. Rather than passing information directly from peer to peer however, a client/server session requires clients to pass information to each other indirectly, through the server. No automatic method exists for the server to pass information from one client to another. If you want this feature available to your users, you need to implement it in the server application.
The Microsoft?DirectX® software development kit (SDK) includes the complete sample code for this tutorial. The sample code can be found at (SDK root)\Samples\C++\DirectPlay\Tutorials\Tut09_ClientServer.
This article contains the following sections.
To play this tutorial sample, first run the server application. To host, click the Host... button and the Host New Session window opens. Enter a session name and click OK. the session status will change to 'Hosting Session "YourSessionName".'
Once the server is hosting, you can run the client application. Click Connect... and the Connect to Session window will open. Enter an Internet Protocol (IP) address in the Search Address box and click Search. The application prints all the sessions found at the address in the Detected Sessions box. The Search button will turn grey while the search is taking place. Select one of the Detected Sessions and click Connect. Once connected, your session status will change to 'Connected to Session "YourSessionName".'
The server and the clients can choose to send a message or disconnect. If sending a message, enter a text string and click Send. If disconnecting, click Disconnect. Click Exit to end the sample.
The server application is where the main part of the game's processing will most likely be done. The server is responsible for updating clients about the game state, for example when a player joins or leaves the session. The following topics in this section describe the tasks needed to set up a server application. Each task is described in detail below.
Creating a Microsoft DirectPlay?server object is similar to Creating a DirectPlay Peer Object. The only difference is that when you call CoCreateInstance, you pass the class identifier of a server object (CLSID_DirectPlay8Server), the identifier of the interface (IID_IDirectPlay8Server), and the address of a pointer to an IDirectPlay8Server interface, instead of the equivalent peer parameters. After you've created the DirectPlay server object, you can initialize it by calling the IDirectPlay8Server::Initialize method.
The following excerpt from the tutorial sample illustrates how to create and initialize a DirectPlay server object.
IDirectPlay8Server *g_pDPServer = NULL; . . . // Create the IDirectPlay8Server object. hr = CoCreateInstance(CLSID_DirectPlay8Server, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Server, (LPVOID*) &g_pDPServer); // Initialize DirectPlay. hr = g_pDPServer->Initialize(NULL, DirectPlayMessageHandlerServer, 0);
In the initialization, you pass the pointer to a Implementing a Callback Function in DirectPlay and DirectPlay Voice, DirectPlayMessageHandlerServer, which handles messages received by the server.
To host a session, you must specify the address of the host device. Do this by creating an IDirectPlay8Address object and calling the IDirectPlay8Address::SetSP method. This step is identical to the step Creating an Address Object in Tutorial 2: Hosting a Session.
To begin hosting a DirectPlay server session, call the IDirectPlay8Server::Host method. This method takes the following parameters.
For more information about the parameters, see IDirectPlay8Server::Host.
The following excerpt from the tutorial sample illustrates how to begin hosting a DirectPlay server session.
WCHAR strHost[128] = {0}; DPN_APPLICATION_DESC dpAppDesc; // Get the session name from dialog if( IDOK != DialogBox( g_hInst, MAKEINTRESOURCE(IDD_HOST), g_hDlg, HostDlgProc ) ) return S_OK; DXUtil_ConvertGenericStringToWideCch( strHost, g_strSession, 128 ); // Set up the Application Description. ZeroMemory(&dpAppDesc, sizeof(DPN_APPLICATION_DESC)); dpAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC); dpAppDesc.dwFlags = DPNSESSION_CLIENT_SERVER; // Flag describing the app dpAppDesc.guidApplication = g_guidApp; // GUID for the app dpAppDesc.pwszSessionName = strHost; // Session name // Host the application. hr = g_pDPServer->Host(&dpAppDesc, // pdnAppDesc &g_pDeviceAddress, 1, // prgpDeviceInfo, cDeviceInfo NULL, NULL, // pdpSecurity, pdpCredentials NULL, // pvPlayerContext 0); // dwFlags
The client application is responsible for handling the user interface (UI) and processing messages from the server. The following topics in this section describe the tasks needed to create a DirectPlay client application. Each task is described in detail below.
Setting up a client object is similar to Creating a DirectPlay Peer Object. The only difference between setting up a client object and a peer object is that when you call CoCreateInstance, instead of passing the equivalent peer parameters, you pass the class identifier of a client object (CLSID_DirectPlay8Client), the identifier of the interface (IID_IDirectPlay8Client), and the address of a pointer to an IDirectPlay8Client interface. After you create the DirectPlay client object, you can initialize it by calling IDirectPlay8Client::Initialize.
The following excerpt from the tutorial sample illustrates how to create and initialize a DirectPlay client object.
// Create the IDirectPlay8Client object. hr = CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Client, (LPVOID*) &g_pDPClient); // Initialize IDirectPlay8Client object. hr = g_pDPClient->Initialize(NULL, DirectPlayMessageHandlerClient, 0);
In the initialization, you pass the pointer to a callback function, DirectPlayMessageHandlerClient, which handles messages the client receives. In this sample, the pvUserContext parameter is set to NULL. However, if you use the same message handler function for multiple interfaces, you should specify a value for pvUserContext. For more information, see Using Player Context Values.
For the client to connect to the server, you must specify the client's device address. Do this by creating an IDirectPlay8Address object and calling the IDirectPlay8Address::SetSP method. This step is identical to the step Creating an Address Object in Tutorial 2: Hosting a Session.
For the client to connect to the server, you must also create an IDirectPlay8Address object for the address of the server. This step is identical to the step Creating an Address Object in Tutorial 2: Hosting a Session.
If you don't know the address of the server to which you want to connect, you can enumerate all of the available servers. Follow the steps in Tutorial 3: Enumerating Hosted Sessions and replace all instances of the pointer to an IDirectPlay8Peer interface with the pointer to an IDirectPlay8Client interface.
To connect to a DirectPlay client/server session, follow the same process you use to connect to a peer-to-peer session except that the IDirectPlay8Client::Connect method does not take the pvPlayerContext parameter. Therefore, the call to IDirectPlay8Client::Connect is as follows:
hr = g_pDPClient->Connect(&dpnAppDesc, // pdnAppDesc pHostAddress, // pHostAddr g_pDeviceAddress, // pDeviceInfo NULL, // pdnSecurity NULL, // pdnCredentials NULL, 0, // pvUserConnectData, Size NULL, // pvAsyncContext NULL, // pvAsyncHandle DPNCONNECT_SYNC); // dwFlags
For more information, see Tutorial 4: Connecting to a Session.
After the server is hosting and a client is connected, the client and server can send messages to each other. If more than one client is connected, the server can send messages to a single player, a group of players, or all the players.
The server can send a message to clients using the IDirectPlay8Server::SendTo method. The following excerpt from the client/server tutorial sample illustrates how to call the IDirectPlay8Server::SendTo method.
hr = g_pDPServer->SendTo(DPNID_ALL_PLAYERS_GROUP, // dpnid &dpnBuffer, // pBufferDesc 1, // cBufferDesc 0, // dwTimeOut NULL, // pvAsyncContext NULL, // pvAsyncHandle DPNSEND_SYNC | // dwFlags DPNSEND_NOLOOPBACK);
Setting the dpnid parameter to DPNID_ALL_PLAYERS_GROUP sends the message to all players connected to the session. To specify a specific player or group, set dpnid to the specific player ID or group ID. The dpnBuffer is a pointer to the DPN_BUFFER_DESC structure that contains the data to send. For a description of the message flags and the other parameters, see IDirectPlay8Server::SendTo.
Messages received from the clients are processed by the DirectPlayMessageHandlerServer function. The message handler function will typically take the following form.
HRESULT WINAPI DirectPlayMessageHandlerServer(PVOID pvUserContext, DWORD dwMessageId, PVOID pMsgBuffer) { switch (dwMessageId) { case DPN_MSGID_RECEIVE: { PDPNMSG_RECEIVE pMsg; pMsg = (PDPNMSG_RECEIVE) pMsgBuffer; //process data break; } . . . //Other cases } return hr; }
When a message is received, DirectPlay generates a DPN_MSGID_RECEIVE message. The DirectPlayMessageHandlerServer function tells your application what to do with the data it received. In this example, the text in the message data buffer displays on the player's screen.
For other messages that you might want to include, see Handling Client/Server Messages.
A client can send messages to and receive messages from only the server. If a client wants to send a message to another client, the message must first be sent to the server. The server application can implement a method to forward the message to other clients.
A client can send a message to the server using the IDirectPlay8Client::Send method. The following example from the client/server tutorial sample illustrates how to call the IDirectPlay8Client::Send method.
hr = g_pDPClient->Send(&dpnBuffer, // pBufferDesc 1, // cBufferDesc 0, // dwTimeOut NULL, // pvAsyncContext NULL, // pvAsyncHandle DPNSEND_SYNC| DPNSEND_NOLOOPBACK); // dwFlags
The pBufferDesc parameter is a pointer to a DPN_BUFFER_DESC structure that tells the application what data to send. The dwTimeOut parameter is set to 0, which means that the message waits in the queue until it is either sent or the connection ends. You can set dwTimeOut to a value so that the message is not sent unless it is sent within the specified number of milliseconds. For a description of the message flags and the other parameters, see IDirectPlay8Client::Send.
Messages received from the server are processed by the DirectPlayMessageHandlerClient function. The client message handler function takes the same form as the DirectPlayMessageHandlerServer function. For more information, see the example of a message handler function in the Receiving Messages from Clients section of Server Messages.
If a DirectPlay client or server object was successfully initialized, you should close the object by calling IDirectPlay8Client::Close or IDirectPlay8Server::Close, and then release all active objects and terminate the application. For further discussion on closing and releasing DirectPlay objects, see Tutorial 1: Creating a DirectPlay Object and Enumerating Service Providers. When a client closes the session, the server receives the DPN_MSGID_DESTROY_PLAYER message but the game will continue if other players are connected. When the server closes the session, the clients receive the DPN_MSGID_TERMINATE_SESSION message and the session ends. For more information about handling these messages, see Handling Client/Server Messages.