本篇是创建游戏内核(24)的续篇,涉及到的DirectPlay基础知识请参阅使用DirectPlay进行网络互联(4)。
使用NETWORK_CLIENT处理客户端
可以使用NETWORK_CLIENT类来处理网络的客户端对象,它的使用方法同NETWORK_SERVER对象的使用方法相似,但有一点不同,那就是连接到网络的方法,必须使用NETWORK_CLIENT::connect来建立与服务器端的连接。
来看看类的定义:
typedef class NETWORK_CLIENT
{
public:
NETWORK_CLIENT();
virtual ~NETWORK_CLIENT();
IDirectPlay8Client* get_client();
BOOL init();
BOOL shutdown();
BOOL connnect(const GUID* guid_adapter,
const char* server_ip, long server_port,
const char* player_name,
const char* session_name, const char* session_pwd = NULL);
BOOL disconnect();
BOOL is_connected();
BOOL send_data(void* data, ulong size, ulong flags = 0);
BOOL send_text(char* text, ulong flags = 0);
BOOL get_local_ip(char* local_ip);
long get_server_port();
BOOL get_name(char* name);
BOOL get_session_name(char* session_name);
BOOL get_session_pwd(char* session_pwd);
protected:
static HRESULT WINAPI _network_msg_handler(PVOID user_context, DWORD msg_id, PVOID msg_buffer);
virtual BOOL _add_player_to_group(DPNMSG_ADD_PLAYER_TO_GROUP* msg) { return TRUE; }
virtual BOOL _async_op_complete(DPNMSG_ASYNC_OP_COMPLETE* msg) { return TRUE; }
virtual BOOL _client_info(DPNMSG_CLIENT_INFO* msg) { return TRUE; }
virtual BOOL _connect_complete(DPNMSG_CONNECT_COMPLETE* msg) { return TRUE; }
virtual BOOL _create_group(DPNMSG_CREATE_GROUP* msg) { return TRUE; }
virtual BOOL _create_player(DPNMSG_CREATE_PLAYER* msg) { return TRUE; }
virtual BOOL _destroy_group(DPNMSG_DESTROY_GROUP* msg) { return TRUE; }
virtual BOOL _destroy_player(DPNMSG_DESTROY_PLAYER* msg) { return TRUE; }
virtual BOOL _enum_hosts_query(DPNMSG_ENUM_HOSTS_QUERY* msg) { return TRUE; }
virtual BOOL _enum_hosts_response(DPNMSG_ENUM_HOSTS_RESPONSE* msg) { return TRUE; }
virtual BOOL _group_info(DPNMSG_GROUP_INFO* msg) { return TRUE; }
virtual BOOL _host_migrate(DPNMSG_HOST_MIGRATE* msg) { return TRUE; }
virtual BOOL _indicate_connect(DPNMSG_INDICATE_CONNECT* msg) { return TRUE; }
virtual BOOL _indicated_connect_aborted(DPNMSG_INDICATED_CONNECT_ABORTED* msg) { return TRUE; }
virtual BOOL _peer_info(DPNMSG_PEER_INFO* msg) { return TRUE; }
virtual BOOL _receive(DPNMSG_RECEIVE* msg) { return TRUE; }
virtual BOOL _remove_player_from_group(DPNMSG_REMOVE_PLAYER_FROM_GROUP* msg) { return TRUE; }
virtual BOOL _return_buffer(DPNMSG_RETURN_BUFFER* msg) { return TRUE; }
virtual BOOL _send_complete(DPNMSG_SEND_COMPLETE* msg) { return TRUE; }
virtual BOOL _server_info(DPNMSG_SERVER_INFO* msg) { return TRUE; }
virtual BOOL _terminate_session(DPNMSG_TERMINATE_SESSION* msg) { return TRUE; }
protected:
IDirectPlay8Client* _client;
BOOL _is_connected;
char _server_ip[MAX_PATH];
long _server_port;
char _name[MAX_PATH];
char _session_name[MAX_PATH];
char _session_pwd[MAX_PATH];
} *NETWORK_CLIENT_PTR;
实现:
//---------------------------------------------------------------------------------
// Constructor, zero member data.
//---------------------------------------------------------------------------------
NETWORK_CLIENT::NETWORK_CLIENT()
{
memset(this, 0, sizeof(*this)-4);
}
//---------------------------------------------------------------------------------
// Destructor, shutdown this client connection.
//---------------------------------------------------------------------------------
NETWORK_CLIENT::~NETWORK_CLIENT()
{
shutdown();
}
//---------------------------------------------------------------------------------
// Shutdown this client connection.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::shutdown()
{
if(! disconnect())
return FALSE;
_client = NULL;
return TRUE;
}
//---------------------------------------------------------------------------------
// Disconnet this client to any clients and servers.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::disconnect()
{
if(_client)
{
if(FAILED(_client->Close(0)))
return FALSE;
}
_is_connected = FALSE;
_server_port = 0;
_server_ip[0] = 0;
_name[0] = 0;
_session_name[0] = 0;
_session_pwd[0] = 0;
return TRUE;
}
//---------------------------------------------------------------------------------
// Return pointer to DirectPlay client object.
//---------------------------------------------------------------------------------
IDirectPlay8Client* NETWORK_CLIENT::get_client()
{
return _client;
}
//---------------------------------------------------------------------------------
// Create DirectPlay client object.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::init()
{
// shutdown older connection first
shutdown();
// create the client object
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Client, NULL, CLSCTX_INPROC, IID_IDirectPlay8Client,
(void**) &_client)))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------------
// Connect to server.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::connnect(const GUID* guid_adapter,
const char* server_ip, long server_port,
const char* player_name,
const char* session_name, const char* session_pwd)
{
// disconnnect older connection first
disconnect();
// error checking
if(_client == NULL || session_name == NULL || player_name == NULL || server_ip == NULL)
return FALSE;
if((_server_port = server_port) == 0)
return FALSE;
// initialize the client object
if(FAILED(_client->Initialize((PVOID)this, _network_msg_handler, 0)))
return FALSE;
// assign client information
DPN_PLAYER_INFO player_info;
WCHAR w_player_name[MAX_PATH];
ZeroMemory(&player_info, sizeof(DPN_PLAYER_INFO));
mbstowcs(w_player_name, player_name, strlen(player_name)+1);
player_info.dwSize = sizeof(DPN_PLAYER_INFO);
player_info.dwInfoFlags = DPNINFO_NAME | DPNINFO_DATA;
player_info.pwszName = w_player_name;
_client->SetClientInfo(&player_info, NULL, NULL, DPNSETCLIENTINFO_SYNC);
// create address objects
IDirectPlay8Address* dp_address = NULL;
IDirectPlay8Address* dp_device = NULL;
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address,
(void**) &dp_address)))
return FALSE;
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address,
(void**) &dp_device)))
{
dp_address->Release();
return FALSE;
}
// set protocols
dp_address->SetSP(&CLSID_DP8SP_TCPIP);
dp_device->SetSP(&CLSID_DP8SP_TCPIP);
// set the port - must not be 0
dp_address->AddComponent(DPNA_KEY_PORT, &_server_port, sizeof(DWORD), DPNA_DATATYPE_DWORD);
WCHAR w_server_ip[MAX_PATH];
// set the host ip address
mbstowcs(w_server_ip, server_ip, strlen(server_ip)+1);
dp_address->AddComponent(DPNA_KEY_HOSTNAME, w_server_ip, (DWORD) ((wcslen(w_server_ip) + 1) * sizeof(WCHAR)),
DPNA_DATATYPE_STRING);
// set the adapter
dp_address->AddComponent(DPNA_KEY_DEVICE, guid_adapter, sizeof(GUID), DPNA_DATATYPE_GUID);
dp_device->AddComponent(DPNA_KEY_DEVICE, guid_adapter, sizeof(GUID), DPNA_DATATYPE_GUID);
WCHAR w_session_name[MAX_PATH], w_session_pwd[MAX_PATH];
// record the session name and password
strcpy(_session_name, session_name);
mbstowcs(w_session_name, session_name, MAX_PATH);
if(session_pwd)
{
strcpy(_session_pwd, session_pwd);
mbstowcs(w_session_pwd, session_pwd, MAX_PATH);
}
// setup the application description structure
DPN_APPLICATION_DESC app_desc;
ZeroMemory(&app_desc, sizeof(DPN_APPLICATION_DESC));
app_desc.dwSize = sizeof(DPN_APPLICATION_DESC);
app_desc.dwFlags = DPNSESSION_CLIENT_SERVER;
app_desc.guidApplication = g_app_guid;
app_desc.pwszSessionName = w_session_name;
if(session_pwd)
{
app_desc.pwszPassword = w_session_pwd;
app_desc.dwFlags |= DPNSESSION_REQUIREPASSWORD;
}
DPNHANDLE async_handle;
BOOL ret_value = TRUE;
// connect to server
HRESULT rv = _client->Connect(&app_desc, dp_address, dp_device, NULL, NULL, NULL, 0, NULL, &async_handle, 0);
if(FAILED(rv))
{
ret_value = FALSE;
if(rv == DPNERR_HOSTREJECTEDCONNECTION)
err_msg_box("Host reject connection.");
else if(rv == DPNERR_INVALIDAPPLICATION)
err_msg_box("The GUID supplied for the application is invalid.");
else if(rv == DPNERR_INVALIDDEVICEADDRESS)
err_msg_box("The address for the local computer or adapter is invalid.");
else if(rv == DPNERR_INVALIDFLAGS)
err_msg_box("The flags passed to this method are invalid.");
else if(rv == DPNERR_INVALIDHOSTADDRESS)
err_msg_box("The specified remote address is invalid.");
else if(rv == DPNERR_INVALIDINSTANCE)
err_msg_box("The GUID for the application instance is invalid.");
else if(rv == DPNERR_INVALIDINTERFACE)
err_msg_box("The interface parameter is invalid. This value will be returned in a connect request"
" if the connecting player was not a client in a client/server game or a peer in a peer-to-peer game.");
else if(rv == DPNERR_INVALIDPASSWORD)
err_msg_box("An invalid password was supplied when attempting to join a session that requires a password.");
else if(rv == DPNERR_NOCONNECTION)
err_msg_box("No communication link was established.");
else if(rv == DPNERR_NOTHOST)
err_msg_box("An attempt by the client to connect to a nonhost computer.");
else if(rv == DPNERR_SESSIONFULL)
err_msg_box("The maximum number of players allotted for the session has been reached.");
else if(rv == DPNERR_ALREADYCONNECTED)
err_msg_box("The object is already connected to the session.");
}
dp_address->Release();
dp_device->Release();
return ret_value;
}
//---------------------------------------------------------------------------------
// Judge whether client has connected to server.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::is_connected()
{
return _is_connected;
}
//---------------------------------------------------------------------------------
// Send data to server.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::send_data(void* data, ulong size, ulong flags)
{
// error checking
if(_client == NULL)
return FALSE;
// build a data structure
DPN_BUFFER_DESC buffer_desc;
buffer_desc.dwBufferSize = size;
buffer_desc.pBufferData = (BYTE*) data;
DPNHANDLE async_handle;
if(FAILED(_client->Send(&buffer_desc, 1, 0, NULL, &async_handle, flags)))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------------
// Send text to server.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::send_text(char* text, ulong flags)
{
// error checking
if(_client == NULL || text == NULL)
return FALSE;
return send_data(text, (ulong)strlen(text) + 1, flags);
}
//---------------------------------------------------------------------------------
// Get local ip address.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::get_local_ip(char* local_ip)
{
WSADATA wsa_data;
if(WSAStartup(MAKEWORD(1, 0), &wsa_data))
return FALSE;
char local_name[MAX_PATH];
gethostname(local_name, MAX_PATH);
HOSTENT* host_ent;
if((host_ent = gethostbyname(local_name)) == NULL)
{
WSACleanup();
return FALSE;
}
char* ip_addr = inet_ntoa(*((in_addr*) host_ent->h_addr_list[0]));
WSACleanup();
if(ip_addr == NULL)
return FALSE;
strcpy(local_ip, ip_addr);
return TRUE;
}
//---------------------------------------------------------------------------------
// Return port which used to connect to server.
//---------------------------------------------------------------------------------
long NETWORK_CLIENT::get_server_port()
{
return _server_port;
}
//---------------------------------------------------------------------------------
// Return name.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::get_name(char* name)
{
if(name == NULL)
return FALSE;
strcpy(name, _name);
return TRUE;
}
//---------------------------------------------------------------------------------
// Get session name.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::get_session_name(char* session_name)
{
if(session_name == NULL)
return FALSE;
strcpy(session_name, _session_name);
return TRUE;
}
//---------------------------------------------------------------------------------
// Get session password.
//---------------------------------------------------------------------------------
BOOL NETWORK_CLIENT::get_session_pwd(char* session_pwd)
{
if(session_pwd == NULL)
return FALSE;
strcpy(session_pwd, _session_pwd);
return TRUE;
}
//---------------------------------------------------------------------------------
// Message handler.
//---------------------------------------------------------------------------------
HRESULT WINAPI NETWORK_CLIENT::_network_msg_handler(PVOID user_context, DWORD msg_id, PVOID msg_buffer)
{
NETWORK_CLIENT_PTR client;
DPNMSG_CONNECT_COMPLETE* msg_connect_complte;
if((client = (NETWORK_CLIENT_PTR) user_context) == NULL)
return E_FAIL;
switch(msg_id)
{
case DPN_MSGID_ADD_PLAYER_TO_GROUP:
if(client->_add_player_to_group((DPNMSG_ADD_PLAYER_TO_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ASYNC_OP_COMPLETE:
if(client->_async_op_complete((DPNMSG_ASYNC_OP_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CLIENT_INFO:
if(client->_client_info((DPNMSG_CLIENT_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CONNECT_COMPLETE:
// update connection information
msg_connect_complte = (DPNMSG_CONNECT_COMPLETE*) msg_buffer;
if(msg_connect_complte->hResultCode == S_OK)
client->_is_connected = TRUE;
if(client->_connect_complete((DPNMSG_CONNECT_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CREATE_GROUP:
if(client->_create_group((DPNMSG_CREATE_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CREATE_PLAYER:
if(client->_create_player((DPNMSG_CREATE_PLAYER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_DESTROY_GROUP:
if(client->_destroy_group((DPNMSG_DESTROY_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_DESTROY_PLAYER:
if(client->_destroy_player((DPNMSG_DESTROY_PLAYER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ENUM_HOSTS_QUERY:
if(client->_enum_hosts_query((DPNMSG_ENUM_HOSTS_QUERY*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ENUM_HOSTS_RESPONSE:
if(client->_enum_hosts_response((DPNMSG_ENUM_HOSTS_RESPONSE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_GROUP_INFO:
if(client->_group_info((DPNMSG_GROUP_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_HOST_MIGRATE:
if(client->_host_migrate((DPNMSG_HOST_MIGRATE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_INDICATE_CONNECT:
if(client->_indicate_connect((DPNMSG_INDICATE_CONNECT*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_INDICATED_CONNECT_ABORTED:
if(client->_indicated_connect_aborted((DPNMSG_INDICATED_CONNECT_ABORTED*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_PEER_INFO:
if(client->_peer_info((DPNMSG_PEER_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_RECEIVE:
if(client->_receive((DPNMSG_RECEIVE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_REMOVE_PLAYER_FROM_GROUP:
if(client->_remove_player_from_group((DPNMSG_REMOVE_PLAYER_FROM_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_RETURN_BUFFER:
if(client->_return_buffer((DPNMSG_RETURN_BUFFER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_SEND_COMPLETE:
if(client->_send_complete((DPNMSG_SEND_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_SERVER_INFO:
if(client->_server_info((DPNMSG_SERVER_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_TERMINATE_SESSION:
client->_is_connected = FALSE;
if(client->_terminate_session((DPNMSG_TERMINATE_SESSION*) msg_buffer))
return S_OK;
break;
}
return E_FAIL;
}
测试代码:
/********************************************************************************
PURPOSE:
Test for class NETWORK_CLIENT.
********************************************************************************/
#include "Core_Global.h"
#define HOST_PORT 12345
class CLIENT : public NETWORK_CLIENT
{
public:
CLIENT()
{
_is_connect_complete = FALSE;
}
BOOL connect_complete()
{
return _is_connect_complete;
}
private:
virtual BOOL _receive(DPNMSG_RECEIVE* msg)
{
MessageBox(NULL, LPCSTR(msg->pReceiveData), "server message", MB_OK);
return TRUE;
}
virtual BOOL _connect_complete(DPNMSG_CONNECT_COMPLETE* Msg)
{
_is_connect_complete = TRUE;
return TRUE;
}
private:
BOOL _is_connect_complete;
};
class APP : public APPLICATION
{
public:
BOOL init()
{
if(! _adapter.init())
return FALSE;
// get first adapter guid
GUID* guid_adapter = _adapter.get_guid(0);
if(! _client.init())
return FALSE;
if(! _client.connnect(guid_adapter, "127.0.0.1", HOST_PORT, "lovedday", "test_session"))
return FALSE;
while(! _client.connect_complete())
;
if(! _client.send_text("Hi, how are you?"))
return FALSE;
return TRUE;
}
BOOL frame()
{
Sleep(300);
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
CLIENT _client;
NETWORK_ADAPTER _adapter;
};
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
return app.run();
}