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

Работа с клиентом

Клиент, в свою очередь, не так сложен как сервер. Он обычно использует только два сообщения, о приеме данных и о прекращении сессии, и устанавливает и отслеживает единственное соединение (с сервером).

Основное дополнение состоит в том, что клиентское приложение должно установить параметры игрока, чтобы главный узел мог получить их. Установка информации об игроке заключается в заполнении соответствующими данными структуры DPN_PLAYER_INFO и последующем вызове функции IDirectPlay8Client::SetClientInfo.

Вам интересны лишь несколько полей структуры DPN_PLAYER_INFO — особенно pwszName, содержащее строку Unicode с именем игрока, которое вы хотите использовать. Вам необходимо очистить структуру, установить значение dwSize, присвоить полю dwInfoFlags значение DPNINFO_NAME | DPNINFO_DATA и задать имя игрока.

Вот прототип функции IDirectPlay8Client::SetClientInfo:

HRESULT IDirectPlay8Client::SetClientInfo(
     const DPN_PLAYER_INFO *const pdpnPlayerInfo, // Данные игрока
     PVOID const pvAsyncContext,                  // NULL
     DPNHANLDE *const phAsyncHandle,              // NULL
     const DWORD dwFlags};                        // DPNSETCLIENTINFO_SYNC

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

ВНИМАНИЕ!
Как уже упоминалось, используйте один и тот же GUID приложения для клиента и для сервера, чтобы они могли распознать друг друга. Невыполнение этого требования — одна из главных причин, по которой сетевые приложения не могут установить соединение, так что убедитесь, что GUID приложений одинаковы.

// GUID клиента/сервера
GUID AppGUID = { 0xede9493e, 0x6ac8, 0x4f15, \
               { 0x8d, 0x1, 0x8b, 0x16, 0x32, 0x0, 0xb9, 0x66 } };

// Прототип обработчика сообщений
HRESULT WINAPI ClientMsgHandler(PVOID pvUserContext,
                            DWORD dwMessageId, PVOID pMsgBuffer);

IDirectPlay8Client *StartClientServer(
     char *szPlayerName,  // Имя игрока
     char *szSessionName, // Имя сессии, к которой
                          // подключаемся (в ANSI)
     char *szPassword,    // Используемый пароль (NULL если не нужен)
     char *szIPAddress,   // Текстовая строка с IP-адресом
                          // в формате ###.###.###.###
     DWORD dwPort)        // Используемый порт
{
    IDirectPlay8Client *pDPClient;
    IDirectPlay8Address *pDPAddress;

    DPN_APPLICATION_DESC dpad;
    DPN_PLAYER_INFO dppi;

    WCHAR wszSessionName[256];
    WCHAR wszPassword[256];
    WCHAR wszIPAddress[256];
    WHCAR wszPlayerName[256];

    // Создание и инициализация объекта клиента
    if(FAILED(DirectPlay8Create(&IID_IDirectPlay8Client,
                                (void**)&pDPClient, NULL)))
        return NULL;
    if(FAILED(pDPServer->Initialize(pDPClient,
                                    ClientMsgHandler, 0))) {
        pDPClient->Release();
        return NULL;
    }

    // Создание объекта адреса и установка поставщика услуг
    if(FAILED(DirectPlay8AddressCreate(&IID_IDirectPlay8Address,
                                       (void**)&pDPAddress, NULL))) {
        pDPClient->Release();
        return NULL;
    }
    pDPAddress->SetSP(&CLDID_DP8SP_TCPIP);

    // Преобразование IP-адреса в Unicode и добавление компонента
    mbstowcs(wszIPAddress, szIPAddress, strlen(szIPAddress)+1);
    pDPAddress->AddComponent(DPNA_KEY_HOSTNAME, wszIPAddress,
                    (wcslen(PlayerInfo->pwszName)+1)*sizeof(WCHAR),
                    DPNA_DATATYPE_STRING);

    // Добавление компонента порта
    pDPAddress->AddComponent(DPNA_KEY_PORT, &dwPort,
                             sizeof(DWORD), DPNA_DATATYPE_DWORD);

    // Установка информации об игроке
    ZeroMemory(&dppi, sizeof(DPN_PLAYER_INFO));
    dppi.dwSize = sizeof(DPN_PLAYER_INFO);
    dppi.dwInfoFlags = DPNINFO_NAME | DPNINFO_DATA;
    mbstowcs(wszPlayerName,szPlayerName, strlen(szPlayerName)+1);
    dppi.pwszName = wszPlayerName;
    pDPClient->SetClientInfo(&dppi, NULL, NULL,
                             DPNSETCLIENTINFO_SYNC);

    // Установка данных сессии
    ZeroMemory(&dpad, sizeof(DPNA_APPLICATION_DESC));
    dpad.dwSize  = sizeof(DPNA_APPLICATION_DESC);
    dpad.dwFlags = DPNSESSION_CLIENT_SERVER;

    // Установка имени сессии с преобразованием ANSI в Unicode
    mbstowcs(wszSessionName, szSessionName,
             strlen(szSessionName)+1);
    dpad.pwszSessionName = wszSessionName;

    // Установка пароля (если надо)
    if(szPassword != NULL) {
        mbstowcs(wszPassword, szPassword, strlen(szPassword)+1);
        dpad.pwszPassword = wszPassword;
        dpad.dwFlags |= DPNSESSION_REQUIREPASSWORD;
    }

Сейчас клиент готов установить соединение, но теперь все будет немного по-другому. Здесь используется функция IDirectPlay8Client::Connect, и вот прототип этой громадины:

HRESULT IDirectPlay8Client::Connect(
     const DPN_APPLICATION_DESC *const pdnAppDesc, // Данные сессии
     IDirectPlay8Address *const pHostAddr,         // Адрес сервера
     IDirectPlay8Address *const pDeviceInfo,       // Локальное устройство
                                                   // для использования
     const DPN_SECURITY_DESC *const pdnSecurity,   // NULL
     const DPN_SECURITY_CREDENTIALS *const pdnCredentials, // NULL
     const void *const pvUserConnectData,          // Данные для отправки
                                                   // при соединении
     const DWORD dwUserConnectDataSize,            // Размер отправляемых данных
     void *const pvAsyncContext,                   // Контекст асинхронной операции
     DPNHANDLE *const phAsyncHandle,               // Асинхронный дескриптор
     const DWORD dwFlags);                         // 0 или DPNCONNECT_SYNC

Я сказал, что функция Connect громадная, но большинство ее аргументов уже описано в предыдущем разделе «Работа с сервером». Самое явное различие — добавление аргумента pDeviceInfo, являющегося объектом IDirectPlay8Address, который содержит локальное устройство, используемое для установки соединения.

Вы узнали как получить объект IDirectPlay8Address в разделе «Использование адресов». Итак, остается dwFlags, который сообщает DirectPlay должно ли подключение работать асинхронно (0) или синхронно (DPNCONNECT_SYNC).

При использовании асинхронного подключения управление возвращается немедленно, и вы должны ждать сообщения DPN_MSGID_CONNECT_COMPLETE, сообщающего об успешном подключении к серверу. При синхронном соединении функция возвратит управление только после установки соединения или при возникновении ошибки.

Теперь, давайте возьмем первое TCP/IP устройство в системе и передадим его этой функции, используя синхронный метод подключения:

    IDirectPlay8Address *pDPDevice = NULL;

    DPN_SERVICE_PROVIDER_INFO *pSP = NULL;
    DPN_SERVICE_PROVIDER_INFO *pSPPtr;

    DWORD dwSize = 0;
    DWORD dwNumSP = 0;
    DWORD i;

    // Запрашиваем размер буфера данных
    if(SUCCEEDED(pDPClient->EnumServiceProviders(
                   &CLSID_DP8SP_TCPIP, NULL, pSP,
                   &dwSize, &dwNumSP, 0))) {

        // Выделяем буфер и снова выполняем перечисление
        pSP = new BYTE[dwSize];
        if(SUCCEEDED(pDPClient->EnumServiceProviders(
                      &CLSID_DP8SP_TCPIP, NULL, pSP,
                      &dwSize, &dwNumSP, 0))) {

            // Перечисление закончено, используем
            // первый экземпляр TCP/IP
            pSPPtr = pSP;
            if(FAILED(DirectPlay8AddressCreate(
                  &IID_IDirectPlay8Address,
                  (void**)&pDPDevice, NULL))) {
                pDPClient->Release();
                pDPAddress->Release();
                return NULL;
            }
            pDPDevice->AddComponent(DPNA_KEY_DEVICE,
                            pSPPtr->guid,
                            sizeof(GUID), DPNA_DATATYPE_GUID);
        }

        // Освобождаем память буфера данных
        delete[] pSP;
    }
    // Устанавливаем соединение
    if(FAILED(pDPClient->Connect(&dpad, &pDPAddress,
             &pDPDevice, NULL, NULL, NULL, 0, NULL, NULL,
             DPNCONNECT_SYNC))) {
        pDPAddress->Release();
        pDPDevice->Release();
        pDPClient->Release();
        return NULL;
    }

    // Освобождаем объект адреса - он больше не нужен
    pDPAddress->Release();
    pDPDevice->Release();

    // Возвращаем объект клиента
    return pDPClient;
}

Получение и отправка сообщений

Получение сообщений на стороне клиента идентично их получению на стороне сервера, так что об этом вы заботитесь внутри функции обработчика сообщений. Что касается отправки, то это работа функции IDirectPlay8Client::Send:

HRESULT IDirectPlay8Client::Send(
     const DPN_BUFFER_DESC *const pBufferDesc, // Отправляемые данные
     const DWORD cBufferDesc,                  // 1
     const DWORD dwTimeOut,                    // Время ожидания
                                               // (в милисекундах)
     void *const pvAsyncContext,               // Указатель на
                                               // асинхронный контекст
     DPNHANDLE *const phAsyncHandle,           // Асинхронный дескриптор
     const DWORD dwFlags);                     // Флаги (те же, что у
                                               // сервера в SendTo)

Вы использовали эти аргументы в разделе «Отправка сообщений сервера», так что я не буду описывать их снова. Вместо этого я приведу пример конструирования пакета игровых данных, который отправляется серверу через функцию Send объекта клиента.

#define MSG_PLAYERSTATE 0x101

typedef struct {
    DWORD dwType;
    DWORD dwXPos, dwYPos, dwHealth;
} sPlayerInfo;

sPlayerInfo PlayerData;

// Отправляем где-нибудь в программе
DPN_BUFFER_DESC dpbd;
dpbd.dwBufferSize = sizeof(sPlayerInfo);
dpbd.pBufferData  = &PlayerData;
PlayerData.dwType = MSG_PLAYERSTATE;

pDPClient->Send(&dpbd, 1, 0, NULL, NULL, DPNSEND_NOCOPY);

Получение идентификатора игрока

После того, как соединение с сервером установлено, наступает время, когда клиенту будет необходим доступ к его идентификатору (такую ситуацию вы увидите в главе 15, где клиент отслеживает игроков по их идентификаторам). Чтобы получить идентификатор игрока клиент должен проанализировать сообщение об установке соединения (DPN_MSGID_CONNECT_COMPLETE). Это сообщение использует следующую структуру данных, содержащую важную информацию о соединении клиента и сервера:

typedef struct _DPNMSG_CONNECT_COMPLETE {
     DWORD dwSize;                     // Размер структуры
     DPNHANDLE hAsyncOp;               // Дескриптор асинхронной операции
     PVOID pvUserContext;              // Контекст пользователя
     HRESULT hResultCode;              // Результат соединения
     PVOID pvApplicationReplyData;     // Ответ на подключение
     DWORD dwApplicationReplyDataSize; // Размер данных подключения
     DPNID dpnidLocal;                 // Локальный идентификатор игрока
} DPNMSG_CONNECT_COMPLETE, *PDPNMSG_CONNECT_COMPLETE;

Сейчас вас интересует только один фрагмент информации — поле dpnidLocal, которое содержит локальный идентификатор игрока. Сейчас, хотя остальная информация и полезна, мы не будем иметь дела с ней. Фактически, я пропущу весь этот хлам и сосредоточусь на dpnidLocal.

В главе 15 вы увидите, что при установке соединения с сервером ваше клиентское приложение сохраняет значение dpnidLocal, чтобы использовать для ссылки на себя. Причина для ссылки на себя в том, что клиент должен хранить информацию о каждом подключенном клиенте — помните, я говорил, что идентификатор игрока является единственным реальным способом отличить одного клиента от другого.

Теперь давайте пополним багаж знаний еще одним фрагментом информации и посмотрим как завершить начатую сессию.

Завершение сессии клиента

Когда приходит время отключить клиента от сессии, вы должны явно сообщить об этом DirectPlay, чтобы были приняты необходимые меры для разрыва соединения. Обрабатывается отключение клиента от сессии функцией IDirectPlay8Client::Close:

pDPClient->Close(0);

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

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