本篇是创建游戏内核(23)的续篇,涉及到的DirectPlay基础知识请参阅使用DirectPlay进行网络互联(2),使用DirectPlay进行网络互联(3)。
使用NETWORK_SERVER处理服务器
可以使用类NETWORK_SERVER来处理网络服务器端,这个类用于初始化DirectPlay服务器对象,主持游戏会话,处理收到和
要发送的网络消息。
来看看它的定义:
//====================================================================================
// This class encapsulate server how to communicate with client.
//====================================================================================
typedef class NETWORK_SERVER
{
public:
NETWORK_SERVER();
virtual ~NETWORK_SERVER();
IDirectPlay8Server* get_server();
BOOL init();
BOOL shutdown();
BOOL host(const GUID* guid_adapter, long port, const char* session_name,
const char* session_pwd = NULL, long max_players = 0);
BOOL disconnect();
BOOL is_connected();
BOOL send_data(DPNID player_id, void* data, ulong size, ulong flags = 0);
BOOL send_text(DPNID player_id, char* text, ulong flags = 0);
BOOL disconnect_player(long player_id);
BOOL get_ip(char* ip, ulong player_id = 0);
BOOL get_name(char* name, ulong player_id);
long get_port();
BOOL get_session_name(char* session_name);
BOOL get_session_pwd(char* session_pwd);
long get_max_players();
long get_cur_players_num();
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:
IDirectPlay8Server* _server;
BOOL _is_connected;
long _port;
char _session_name[MAX_PATH];
char _session_pwd[MAX_PATH];
long _max_players;
long _cur_players_num;
} *NETWORK_SERVER_PTR;
类的实现:
//---------------------------------------------------------------------------------
// Constructor, zero member data.
//---------------------------------------------------------------------------------
NETWORK_SERVER::NETWORK_SERVER()
{
memset(this, 0, sizeof(*this) - 4);
}
//---------------------------------------------------------------------------------
// Destructor, shutdown server.
//---------------------------------------------------------------------------------
NETWORK_SERVER::~NETWORK_SERVER()
{
shutdown();
}
//---------------------------------------------------------------------------------
// Shutdown server.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::shutdown()
{
if(! disconnect())
return FALSE;
_server = NULL;
return TRUE;
}
//---------------------------------------------------------------------------------
// Disconnect server to all clients.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::disconnect()
{
if(_server)
{
// closes the open connection to a session
if(FAILED(_server->Close(0)))
return FALSE;
}
_is_connected = FALSE;
_port = 0;
_session_name[0] = NULL;
_session_pwd[0] = NULL;
_max_players = 0;
_cur_players_num = 0;
return TRUE;
}
//---------------------------------------------------------------------------------
// Return pointer to IDirectPlay8Server object.
//---------------------------------------------------------------------------------
IDirectPlay8Server* NETWORK_SERVER::get_server()
{
return _server;
}
//---------------------------------------------------------------------------------
// Create IDirectPlay8Server object.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::init()
{
// shutdown old server object first
shutdown();
// create the server object
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Server, NULL, CLSCTX_INPROC, IID_IDirectPlay8Server,
(void**) &_server)))
{
return FALSE;
}
return TRUE;
}
//---------------------------------------------------------------------------------
// Host server.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::host(const GUID* guid_adapter, long port,
const char* session_name, const char* session_pwd, long max_players)
{
// disconnect from current connection
disconnect();
_port = port;
// error checking
if(_server == NULL || session_name == NULL || port == 0)
return FALSE;
// initialize the server object
if(FAILED(_server->Initialize((PVOID)this, _network_msg_handler, 0)))
return FALSE;
IDirectPlay8Address* dp_address;
// create an address object
if(FAILED(CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address,
(void**) &dp_address)))
return FALSE;
DPN_APPLICATION_DESC app_desc;
WCHAR w_session_name[MAX_PATH], w_session_pwd[MAX_PATH];
// set protocol
if(FAILED(dp_address->SetSP(&CLSID_DP8SP_TCPIP)))
goto fail;
// set the port - must not be 0
if(FAILED(dp_address->AddComponent(DPNA_KEY_PORT, &_port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
goto fail;
// set adapter (if any)
if(guid_adapter)
{
if(FAILED(dp_address->AddComponent(DPNA_KEY_DEVICE, guid_adapter, sizeof(GUID), DPNA_DATATYPE_GUID)))
goto fail;
}
// 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
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;
}
_max_players = max_players;
app_desc.dwMaxPlayers = max_players;
// start the hosting
if(FAILED(_server->Host(&app_desc, &dp_address, 1, NULL, NULL, NULL, 0)))
goto fail;
dp_address->Release();
_is_connected = TRUE;
return TRUE;
fail:
dp_address->Release();
return FALSE;
}
//---------------------------------------------------------------------------------
// Check whether server is hosting now.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::is_connected()
{
return _is_connected;
}
//---------------------------------------------------------------------------------
// Send data to client.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::send_data(DPNID player_id, void* data, ulong size, ulong flags)
{
// error checking
if(_server == NULL || size == 0)
return FALSE;
DPNHANDLE async_handle;
DPN_BUFFER_DESC buf_desc;
buf_desc.dwBufferSize = size;
buf_desc.pBufferData = (BYTE*) data;
if(FAILED(_server->SendTo(player_id, &buf_desc, 1, 0, NULL, &async_handle, flags)))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------------
// Send string to client.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::send_text(DPNID player_id, char* text, ulong flags)
{
// error checking
if(_server == NULL || text == NULL)
return FALSE;
return send_data(player_id, text, (ulong)strlen(text)+1, flags);
}
//---------------------------------------------------------------------------------
// Disconnect specified player.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::disconnect_player(long player_id)
{
// error checking
if(_server == NULL)
return FALSE;
// try to disconnect the specified player
if(FAILED(_server->DestroyClient(player_id, NULL, 0, 0)))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------------
// Get ip address, include server and client ip address.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::get_ip(char* ip, ulong player_id)
{
// get host ip address if player_id == 0
if(player_id == 0)
{
WSADATA wsa_data;
// initiates use of ws2_32.dll by a process
if(WSAStartup(MAKEWORD(1, 0), &wsa_data))
return FALSE;
char local_name[MAX_PATH];
// retrieves the standard host name for the local computer
gethostname(local_name, MAX_PATH);
// store information about a given host, such as host name, ip address, and so forth.
HOSTENT* host_ent;
// retrieves host information corresponding to a host name from a host database
if((host_ent = gethostbyname(local_name)) == NULL)
{
// terminates use of the ws2_32.dll
WSACleanup();
return FALSE;
}
// get localip
char* local_ip = inet_ntoa(*(in_addr*)host_ent->h_addr_list[0]);
WSACleanup();
if(local_ip == NULL)
return FALSE;
strcpy(ip, local_ip);
return TRUE;
}
if(_server == NULL || !_is_connected)
return FALSE;
IDirectPlay8Address* dp_address;
// retrieve the player information
if(FAILED(_server->GetClientAddress((DPNID)player_id, &dp_address, 0)))
return FALSE;
DWORD size = MAX_PATH;
DWORD flags = DPNA_DATATYPE_STRING;
WCHAR client_address[MAX_PATH];
// retrieves information on the component at the specified key
if(FAILED(dp_address->GetComponentByName(DPNA_KEY_HOSTNAME, client_address, &size, &flags)))
{
dp_address->Release();
return FALSE;
}
wcstombs(ip, client_address, MAX_PATH);
dp_address->Release();
return TRUE;
}
//---------------------------------------------------------------------------------
// Get player name.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::get_name(char* name, ulong player_id)
{
if(_server == NULL)
return FALSE;
DPN_PLAYER_INFO* player_info = NULL;
DWORD size = 0;
// retrieves the client information set for the specified client
HRESULT rv = _server->GetClientInfo(player_id, player_info, &size, 0);
if(FAILED(rv) && rv != DPNERR_BUFFERTOOSMALL)
return FALSE;
player_info = (DPN_PLAYER_INFO*) new char[size];
player_info->dwSize = sizeof(DPN_PLAYER_INFO);
if(FAILED(_server->GetClientInfo(player_id, player_info, &size, 0)))
{
delete[] player_info;
return FALSE;
}
wcstombs(name, player_info->pwszName, MAX_PATH);
delete[] player_info;
return TRUE;
}
//---------------------------------------------------------------------------------
// Return port of server.
//---------------------------------------------------------------------------------
long NETWORK_SERVER::get_port()
{
return _port;
}
//---------------------------------------------------------------------------------
// Get session name.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::get_session_name(char* session_name)
{
if(session_name == NULL)
return FALSE;
strcpy(session_name, _session_name);
return TRUE;
}
//---------------------------------------------------------------------------------
// Get session password.
//---------------------------------------------------------------------------------
BOOL NETWORK_SERVER::get_session_pwd(char* session_pwd)
{
if(session_pwd == NULL)
return FALSE;
strcpy(session_pwd, _session_pwd);
return TRUE;
}
//---------------------------------------------------------------------------------
// Return max players permitted to login.
//---------------------------------------------------------------------------------
long NETWORK_SERVER::get_max_players()
{
return _max_players;
}
//---------------------------------------------------------------------------------
// Return current player number.
//---------------------------------------------------------------------------------
long NETWORK_SERVER::get_cur_players_num()
{
return _cur_players_num;
}
//---------------------------------------------------------------------------------
// Callback function to handler message for server.
//---------------------------------------------------------------------------------
HRESULT WINAPI NETWORK_SERVER::_network_msg_handler(PVOID user_context, DWORD msg_id, PVOID msg_buffer)
{
NETWORK_SERVER* server;
if((server = (NETWORK_SERVER*) user_context) == NULL)
return E_FAIL;
// pass message to user-defined function
switch(msg_id)
{
case DPN_MSGID_ADD_PLAYER_TO_GROUP:
if(server->_add_player_to_group((DPNMSG_ADD_PLAYER_TO_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ASYNC_OP_COMPLETE:
if(server->_async_op_complete((DPNMSG_ASYNC_OP_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CLIENT_INFO:
if(server->_client_info((DPNMSG_CLIENT_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CONNECT_COMPLETE:
if(server->_connect_complete((DPNMSG_CONNECT_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CREATE_GROUP:
if(server->_create_group((DPNMSG_CREATE_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_CREATE_PLAYER:
if(server->_create_player((DPNMSG_CREATE_PLAYER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_DESTROY_GROUP:
if(server->_destroy_group((DPNMSG_DESTROY_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_DESTROY_PLAYER:
if(server->_destroy_player((DPNMSG_DESTROY_PLAYER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ENUM_HOSTS_QUERY:
if(server->_enum_hosts_query((DPNMSG_ENUM_HOSTS_QUERY*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_ENUM_HOSTS_RESPONSE:
if(server->_enum_hosts_response((DPNMSG_ENUM_HOSTS_RESPONSE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_GROUP_INFO:
if(server->_group_info((DPNMSG_GROUP_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_HOST_MIGRATE:
if(server->_host_migrate((DPNMSG_HOST_MIGRATE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_INDICATE_CONNECT:
if(server->_indicate_connect((DPNMSG_INDICATE_CONNECT*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_INDICATED_CONNECT_ABORTED:
if(server->_indicated_connect_aborted((DPNMSG_INDICATED_CONNECT_ABORTED*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_PEER_INFO:
if(server->_peer_info((DPNMSG_PEER_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_RECEIVE:
if(server->_receive((DPNMSG_RECEIVE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_REMOVE_PLAYER_FROM_GROUP:
if(server->_remove_player_from_group((DPNMSG_REMOVE_PLAYER_FROM_GROUP*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_RETURN_BUFFER:
if(server->_return_buffer((DPNMSG_RETURN_BUFFER*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_SEND_COMPLETE:
if(server->_send_complete((DPNMSG_SEND_COMPLETE*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_SERVER_INFO:
if(server->_server_info((DPNMSG_SERVER_INFO*) msg_buffer))
return S_OK;
break;
case DPN_MSGID_TERMINATE_SESSION:
if(server->_terminate_session((DPNMSG_TERMINATE_SESSION*) msg_buffer))
return S_OK;
break;
}
return E_FAIL;
}
要使用NETWORK_SERVER
类,需要将NETWORK_SERVER
类作为基类派生出自己的类。这样做的原因在于必须重载网络处理函数以满足自己具体的功能需要,NETWORK_SERVER
类中包含了每条网络消息,因此派生出来的类绝不会丢失重要的消息。
要主持游戏会话,需要适配器GUID、会话名称、可选的密码以及允许的玩家最大数目(使用0表示不限制玩家数目)。当调用NETWORK_SERVER::host函数时,DirectPlay就会初始化连接并将控制权返回给使用者。现在,假设有消息发过来,那么就应该接收发送过来的消息并在合适的时候处理它们。创建的每个消息处理函数,如果返回TRUE就表明消息被成功处理,FALSE表明有错误发生。
接着我们编写一段代码来测试NETWORK_SERVER类:
/********************************************************************************
PURPOSE:
Test for class NETWORK_SERVER.
********************************************************************************/
#include "Core_Global.h"
#define HOST_PORT 12345
class SERVER : public NETWORK_SERVER
{
private:
virtual BOOL _receive(DPNMSG_RECEIVE* msg)
{
MessageBox(NULL, LPCSTR(msg->pReceiveData), "client message", MB_OK);
// send data back to client
return send_data(msg->dpnidSender, msg->pReceiveData, msg->dwReceiveDataSize, DPNSEND_GUARANTEED);
}
};
class APP : public APPLICATION
{
public:
BOOL init()
{
if(! _adapter.init())
return FALSE;
// get first adapter guid
GUID* guid_adapter = _adapter.get_guid(0);
if(! _server.init())
return FALSE;
if(! _server.host(guid_adapter, HOST_PORT, "test_session"))
return FALSE;
return TRUE;
}
BOOL frame()
{
Sleep(300);
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
SERVER _server;
NETWORK_ADAPTER _adapter;
};
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
return app.run();
}