netlib.narod.ru< Назад | Оглавление | Далее >

Сетевое ядро

В предыдущей главе вы увидели, как просто использовать DirectPlay. Теперь вы узнаете, как работать с DirectPlay, используя сетевое ядро. Сетевое ядро содержит три класса: cNetworkAdapter, cNetworkServer и cNetworkClient.

Запрос адаптера с cNetworkAdapter

Вы используете cNetworkAdapter для перечисления установленных в вашей системе TCP/IP-устройств. Чтобы установить соединение вам необходимо знать GUID устройства, и его получение является целью класса cNetworkAdapter. Вот как выглядит объявление класса:

class cNetworkAdapter
{
  protected:
    DPN_SERVICE_PROVIDER_INFO *m_AdapterList; // Список адаптеров
    unsigned long m_NumAdapters;              // Количество адаптеров

    // Пустой обработчик сетевых сообщений - необходим
    static HRESULT WINAPI NetMsgHandler(
         PVOID pvUserContext, DWORD dwMessageId,
         PVOID pMsgBuffer) { return S_OK; }

  public:
    cNetworkAdapter();  // Конструктор
    ~cNetworkAdapter(); // Деструктор

    BOOL Init();     // Инициализация объекта класса
    BOOL Shutdown(); // Завершение работы объекта
                     // (освобождение памяти)

    long GetNumAdapters(); // Получение количества
                           // установленных адаптеров

    // Сохранение имени адаптера в буфере
    // (Num от 0 до количества адаптеров минус 1)
    BOOL GetName(unsigned long Num, char *Buf);

    // Возвращает указатель на GUID адаптера
    // (Num от 0 до количества адаптеров минус 1)
    GUID *GetGUID(unsigned long Num);
};

Использовать класс cNetworkAdapter просто: вызовите функцию Init, запросите количество установленных адаптеров, а затем начинайте извлекать имена адаптеров и их GUID. Когда завершите работу с объектом, вызовите Shutdown для освобождения внутренних ресурсов класса.

Я вернусь к использованию класса cNetworkAdapter в следующих двух подразделах. А сейчас давайте перейдем к классу cNetworkServer.

Серверы и cNetworkServer

На серверной стороне сети вы имеете дело с классом cNetworkServer, который позволяет инициализировать серверные объекты DirectPlay, открыть игровую сессию и обрабатывать входящие и исходящие сетевые сообщения. В данном разделе вы увидите, как я оборачиваю операции серверной стороны в представленный ниже класс cNetworkServer:

class cNetworkServer
{
  protected:
    IDirectPlay8Server *m_pDPServer; // Объект сервера

    BOOL m_Connected;                // Флаг запуска узла

    // Имя и пароль сессии (хранятся как ASCII-символы)
    char m_SessionName[MAX_PATH];
    char m_SessionPassword[MAX_PATH];

    long m_Port;       // Используемый порт
    long m_MaxPlayers; // Максимально допустимое
                       // количество игроков
    long m_NumPlayers; // Текущее количество игроков

    // Обработчик сетевых сообщений
    static HRESULT WINAPI NetworkMessageHandler(
         PVOID pvUserContext, DWORD dwMessageId,
         PVOID pMsgBuffer);

    // Перегружаемые функции для различных
    // сетевых сообщений
    virtual BOOL AddPlayerToGroup(
         DPNMSG_ADD_PLAYER_TO_GROUP *Msg) { return TRUE; }
    virtual BOOL AsyncOpComplete(
         DPNMSG_ASYNC_OP_COMPLETE *Msg) { return TRUE; }
    virtual BOOL ClientInfo(
         DPNMSG_CLIENT_INFO *Msg) { return TRUE; }
    virtual BOOL ConnectComplete(
         DPNMSG_CONNECT_COMPLETE *Msg) { return TRUE; }
    virtual BOOL CreateGroup(
         DPNMSG_CREATE_GROUP *Msg) { return TRUE; }
    virtual BOOL CreatePlayer(
         DPNMSG_CREATE_PLAYER *Msg) { return TRUE; }
    virtual BOOL DestroyGroup(
         DPNMSG_DESTROY_GROUP *Msg) { return TRUE; }
    virtual BOOL DestroyPlayer(
         DPNMSG_DESTROY_PLAYER *Msg) { return TRUE; }
    virtual BOOL EnumHostsQuery(
         DPNMSG_ENUM_HOSTS_QUERY *Msg) { return TRUE; }
    virtual BOOL EnumHostsResponse(
         DPNMSG_ENUM_HOSTS_RESPONSE *Msg) { return TRUE; }
    virtual BOOL GroupInfo(
         DPNMSG_GROUP_INFO *Msg) { return TRUE; }
    virtual BOOL HostMigrate(
         DPNMSG_HOST_MIGRATE *Msg) { return TRUE; }
    virtual BOOL IndicateConnect(
         DPNMSG_INDICATE_CONNECT *Msg) { return TRUE; }
    virtual BOOL IndicatedConnectAborted(
         DPNMSG_INDICATED_CONNECT_ABORTED *Msg) { return TRUE; }
    virtual BOOL PeerInfo(
         DPNMSG_PEER_INFO *Msg) { return TRUE; }
    virtual BOOL Receive(
         DPNMSG_RECEIVE *Msg) { return TRUE; }
    virtual BOOL RemovePlayerFromGroup(
         DPNMSG_REMOVE_PLAYER_FROM_GROUP *Msg) { return TRUE; }
    virtual BOOL ReturnBuffer(
         DPNMSG_RETURN_BUFFER *Msg) { return TRUE; }
    virtual BOOL SendComplete(
         DPNMSG_SEND_COMPLETE *Msg) { return TRUE; }
    virtual BOOL ServerInfo(
         DPNMSG_SERVER_INFO *Msg) { return TRUE; }
    virtual BOOL TerminateSession(
         DPNMSG_TERMINATE_SESSION *Msg) { return TRUE; }

  public:
    cNetworkServer();  // Конструктор
    ~cNetworkServer(); // Деструктор

    IDirectPlay8Server *GetServerCOM(); // Возвращает объект сервера

    BOOL Init();     // Инициализирует сетевой сервер
    BOOL Shutdown(); // Выключает сетевой сервер

    // Начинает сессию на узле
    BOOL Host(GUID *guidAdapter, long Port,
              char *SessionName, char *Password = NULL,
              long MaxPlayers = 0);
    BOOL Disconnect();  // Завершение сессии
    BOOL IsConnected(); // Проверяет, запущена ли сессия

    // Передача необработанных данных или текстовой строки
    BOOL Send(DPNID dpnidPlayer, void *Data,
              unsigned long Size, unsigned long Flags=0);
    BOOL SendText(DPNID dpnidPlayer, char *Text,
                  unsigned long Flags=0);

    // Принудительное отключение игрока
    BOOL DisconnectPlayer(long PlayerId);

    // Получение IP-адреса игрока или сервера в указанный буфер
    BOOL GetIP(char *IPAddress, unsigned long PlayerId = 0);

    // Получение имени игрока
    BOOL GetName(char *Name, unsigned long PlayerId);

    // Получение номера используемого порта
    long GetPort();

    // Иолучение имени сессии и пароля
    BOOL GetSessionName(char *Buf);
    BOOL GetSessionPassword(char *Buf);

    // Получение максимально возможного и текущего
    // количества игроков
    long GetMaxPlayers();
    long GetNumPlayers();
};

Для того, чтобы использовать класс cNetworkServer (точно так же как и класс cNetworkClient, о котором мы поговорим в следующем разделе), вы наследуете собственный класс, используя cNetworkServer в качестве базового. Это необходимо потому что требуется перегрузка обработчиков сообщений вашими собственными функциями. В классе cNetworkServer представлены все сетевые сообщения, так что наследуемый класс не пропустит важной информации.

Чтобы начать сессию вам необходим GUID адаптера, имя сессии, необязательный пароль и максимально возможное количество игроков (0 означает, что ограничений нет). При вызове cNetworkServer::Host DirectPlay инициализирует подключения и возвращает управление вам. После этого вы можете начать ожидание входящих сообщений. Ваша работа — закачивать входящие сообщения и поступать с ними так, как считаете нужным. Создаваемые вами обработчики сообщений должны возвращать TRUE, если сообщение успешно обработано, и FALSE, если произошла ошибка.

В качестве примера здесь приведен экземпляр класса cNetworkServer, который отображает текст входящего сообщения и отсылает точно такое же сообщение назад отправителю (используя гарантированную доставку):

// Создаем класс-наследник
class cServer : public cNetworkServer
{
  private:
    BOOL Receive(DPNMSG_RECEIVE *Msg);
};

BOOL cServer::Receive(DPNMSG_RECEIVE *Msg)
{
    // Отображаем сообщение
    MessageBox(NULL, Msg->pReceivedData,
               "Incoming Message", MB_OK);

    // Отправляем его обратно
    Send(Msg->dpnidSender, Msg->pReceiveData,
         Msg->dwReceivedDataSize, DPNSEND_GUARANTEED);

    return TRUE;
}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,
                   LPSTR szCmdLine, int nCmdShow)
{
    cServer         Server;
    cNetworkAdapter Adapter;
    GUID            *guidAdapter;

    // Выбираем первый сетевой адаптер
    Adapter.Init();
    guidAdapter = Adapter.GetGUID(0); // 0 = первый адаптер
    Server.Init();
    Server.Host(guidAdapter, 12345, "TextSession");

    // Ждем нажатия ESC
    while(!(GetAsyncKeyState(VK_ESC) & 0x80));

    Server.Disconnect();
    Server.Shutdown();

    // Освобождаем список адаптеров
    Adapter.Shutdown();

    return TRUE;
}

Не беспокойтесь, если вам показалось, что приведенный пример показывает слишком мало; вы ближе познакомитесь с сетевыми компонентами в главе 15, «Сетевой режим для многопользовательской игры».

Работа клиента и cNetworkClient

Пришло время взглянуть на последний сетевой класс, cNetworkClient, который работает на клиентской стороне сети:

class cNetworkClient
{
  protected:
    IDirectPlay8Client *m_pDPClient; // Объект клиента DP

    BOOL m_Connected; // Флаг подключения

    char m_IPAddress[MAX_PATH]; // IP-адрес
    long m_Port;                // Порт подключения
    char m_Name[MAX_PATH];      // Имя клиента

    // Имя и пароль сессии (пересылается серверу)
    char m_SessionName[MAX_PATH];
    char m_SessionPassword[MAX_PATH];

    // Функция обработки сетевых сообщений
    static HRESULT WINAPI NetworkMessageHandler(
         PVOID pvUserContext, DWORD dwMessageId,
         PVOID pMsgBuffer);

    // Далее идут перегружаемые обработчики сетевых
    // сообщений (такие же, как и в cNetworkServer).
    // Здесь они не приводятся для экономии места

  public:
    cNetworkClient();  // Конструктор
    ~cNetworkClient(); // Деструктор

    // Возвращает объект клиента DirectPlay
    IDirectPlay8Client *GetClientCOM();

    BOOL Init();     // Инициализация сетевого клиента
    BOOL Shutdown(); // Выключение клиента

    // Подключение к удаленному серверу с использованием
    // указанного адаптера, IP, порта, имени игрока,
    // имени сессии, и необязательного пароля
    BOOL Connect(GUID *guidAdapter, char *IP, long Port,
                 char *PlayerName, char *SessionName,
                 char *SessionPassword = NULL);

    BOOL Disconnect();   // Отключение от сессии
    BOOL IsConnected(); // Возвращает TRUE при наличии подключения

    // Функции передачи необработанных данных и текста
    BOOL Send(void *Data, unsigned long Size,
              unsigned long Flags=0);
    BOOL SendText(char *Text, unsigned long Flags=0);

    BOOL GetIP(char *IPAddress);    // Получение IP-адреса в буфер
    long GetPort();                 // Возвращает номер порта
                                    // подключения
    BOOL GetName(char *Name);       // Возвращает имя
    BOOL GetSessionName(char *Buf); // Возвращает имя сессии
    BOOL GetSessionPassword(char *Buf); // Возвращает пароль
};

Работа с cNetworkClient похожа на использование cNetworkServer, за исключением подключения к сети. Чтобы установить подключение, используя cNetworkClient::Connect, вы должны сперва выбрать сетевой адаптер, используя объект класса cNetworkAdapter, как показано в следующем примере:

// Создаем класс-наследник
class cClient : public cNetworkClient
{
  private:
    BOOL Receive(DPNMSG_RECEIVE *Msg);
};

BOOL cClient::Receive(DPNMSG_RECEIVE *Msg)
{
    MessageBox(NULL, Msg->pReceiveData,
               "Incoming Message", MB_OK);
    return TRUE;
}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,
                   LPSTR szCmdLine, int nCmdShow)
{
    cClient         Client;
    cNetworkAdapter Adapter;
    GUID            *guidAdapter;

    // Выбираем первый сетевой адаптер
    Adapter.Init();
    guidAdapter = Adapter.GetGUID(0); // 0 = первый адаптер

    // Инициализируем клиента и подключаемся к IP=123.123.123.123
    // используя порт 12345, сессия = TextSession без пароля.
    Client.Init();
    Client.Connect(guidAdapter, "123.123.123.123",
                   12345, "MyName", "TextSession");

    // Ждем установки соединения,
    // или пока пользователь не нажмет ESC.
    while(Client.IsConnected() == FALSE) {
        if(GetAsyncKeyState(VK_ESC) & 0x80) {
            Client.Disconnect();
            Client.Shutdown();
            return TRUE;
        }
    }

    // Подключились, начинаем прикладную обработку
    // Отправляем текстовое сообщение
    Client.SendText("Hello there!");

    // Ждем нажатия ESC
    while(!(GetAsyncKeyState(VK_ESC) & 0x80));

    // Разрыв соединения и выключение
    Client.Disconnect();
    Client.Shutdown();

    // Освобождение списка адаптеров
    Adapter.Shutdown();

    return TRUE;
}

И снова пример минимален; все сетевое ядро в действии вы увидите в главе 15. Сейчас можете посмотреть на объявление класса и на код реализации, находящийся на прилагаемом к книге компакт-диске.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz