Поддержка сети в играх

Узнайте, как разрабатывать и включать сетевые функции в игру DirectX.

Краткий обзор понятий

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

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

Для одноранговых игр приложение каждого игрока обрабатывает входные и графические данные. В большинстве случаев игроки игры находятся в близком расположении, чтобы задержка сети должна быть ниже, но по-прежнему является проблемой. Как обнаружить одноранговые узлы и установить подключение становится проблемой.

Для игр с одним игроком центральный веб-сервер или служба часто используется для хранения имен пользователей, оценки игр и других прочих сведений. В этих играх скорость и задержка сетевых передач меньше проблем, так как она не влияет непосредственно на игру.

Сетевые условия могут изменяться в любое время, поэтому любая игра, использующая сетевые API, должна обрабатывать исключения сети, которые могут возникнуть. Дополнительные сведения об обработке сетевых исключений см. в статье "Основы сети".

Брандмауэры и веб-прокси являются общими и могут повлиять на возможность использования сетевых функций. Игра, использующая сеть, должна быть подготовлена для правильной обработки брандмауэров и прокси-серверов.

Для мобильных устройств важно отслеживать доступные сетевые ресурсы и вести себя соответствующим образом в сетях с лимитным доступом, где расходы на роуминг или данные могут быть значительными.

Сетевая изоляция является частью модели безопасности приложений, используемой Windows. Windows активно обнаруживает границы сети и применяет ограничения доступа к сети для изоляции сети. Приложения должны объявлять возможности сетевой изоляции, чтобы определить область сетевого доступа. Без объявления этих возможностей ваше приложение не будет иметь доступа к сетевым ресурсам. Дополнительные сведения о том, как Windows применяет сетевую изоляцию для приложений, см. в статье "Настройка возможностей сетевой изоляции".

Рекомендации по проектированию

В играх DirectX можно использовать различные сетевые API. Поэтому важно выбрать правильный API. Windows поддерживает различные сетевые API, которые приложение может использовать для взаимодействия с другими компьютерами и устройствами через Интернет или частные сети. Первым шагом является определение сетей, необходимых вашему приложению.

Это более популярные сетевые API для игр.

  • TCP и сокеты — обеспечивает надежное подключение. Используйте TCP для игровых операций, которые не нуждаются в безопасности. TCP позволяет серверу легко масштабироваться, поэтому обычно используется в играх, использующих модель инфраструктуры (клиент-сервер или одноранговая модель интернета). TCP также можно использовать в нерегламентированных (локальных одноранговых) играх через Wi-Fi Direct и Bluetooth. TCP обычно используется для перемещения объектов игры, взаимодействия символов, текстового чата и других операций. Класс StreamSocket предоставляет сокет TCP, который можно использовать в играх Microsoft Store. Класс StreamSocket используется со связанными классами в пространстве имен Windows::Networking::Sockets .
  • TCP и сокеты с помощью SSL — обеспечивает надежное подключение, которое предотвращает перехват. Используйте TCP-подключения с SSL для игровых операций, требующих безопасности. Шифрование и издержки SSL добавляют затраты на задержку и производительность, поэтому он используется только при необходимости обеспечения безопасности. ПРОТОКОЛ TCP с SSL обычно используется для входа, приобретения и торговли активами, создания и управления игровыми символами. Класс StreamSocket предоставляет сокет TCP, поддерживающий SSL.
  • UDP и сокеты — обеспечивает ненадежную передачу сети с низкими затратами. UDP используется для игровых операций, требующих низкой задержки и может допускать некоторые потери пакетов. Это часто используется для боевых игр, стрельбы и трассировщиков, сетевого звука и голосового чата. Класс DatagramSocket предоставляет сокет UDP, который можно использовать в играх Microsoft Store. Класс DatagramSocket используется со связанными классами в пространстве имен Windows::Networking::Sockets .
  • HTTP-клиент — обеспечивает надежное подключение к HTTP-серверам. Наиболее распространенный сценарий сетевого взаимодействия — доступ к веб-сайту для получения или хранения информации. Простым примером будет игра, использующая веб-сайт для хранения сведений о пользователях и оценках игры. При использовании с SSL для обеспечения безопасности http-клиент может использоваться для входа, приобретения, торговли активами, создания игровых символов и управления. Класс HttpClient предоставляет современный API клиента HTTP для использования в играх Microsoft Store. Класс HttpClient используется со связанными классами в пространстве имен Windows::Web::Http .

Обработка сетевых исключений в игре DirectX

Если в игре DirectX возникает исключение сети, это означает значительную проблему или сбой. Исключения могут возникать по многим причинам при использовании сетевых API. Часто исключение может привести к изменениям в сетевом подключении или других сетевых проблемах с удаленным узлом или сервером.

Ниже приведены некоторые причины исключений при использовании сетевых API:

  • Входные данные пользователя для имени узла или URI содержат ошибки и недопустимы.
  • Сбои разрешения имен при поиске имени узла или URi.
  • Потеря или изменение сетевого подключения.
  • Сбои сетевого подключения с помощью сокетов или API-интерфейсов КЛИЕНТА HTTP.
  • Ошибки сетевого сервера или удаленной конечной точки.
  • Другие ошибки сети.

Исключения из сетевых ошибок (например, потеря или изменение подключения, сбоев подключений и сбоев сервера) могут произойти в любое время. Эти ошибки приводят к возникновению исключений. Если исключение не обрабатывается приложением, это может привести к тому, что все приложение будет завершено средой выполнения.

Необходимо написать код для обработки исключений при вызове большинства асинхронных сетевых методов. Иногда при возникновении исключения сетевой метод может быть извлечен как способ устранения проблемы. В других случаях приложению может потребоваться продолжить работу без сетевого подключения с использованием ранее кэшированных данных.

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

При возникновении исключения в игре DirectX, которая является приложением UWP, можно получить значение HRESULT для причины ошибки. Файл Winerror.h содержит большой список возможных значений HRESULT , включающих сетевые ошибки.

Сетевые API поддерживают различные методы получения подробных сведений о причине исключения.

  • Метод для получения значения HRESULT ошибки, вызвавшей исключение. Возможный список потенциальных значений HRESULT велик и не указан. Значение HRESULT можно получить при использовании любого из сетевых API.
  • Вспомогательный метод, который преобразует значение HRESULT в значение перечисления. Список возможных значений перечисления указан и относительно небольшой. Вспомогательный метод доступен для классов сокетов в Windows::Networking::Sockets.

Исключения в Windows.Networking.Sockets

Конструктор класса HostName , используемый сокетами, может вызвать исключение, если переданная строка не является допустимым именем узла (содержит символы, которые не разрешены в имени узла). Если приложение получает входные данные от пользователя для имени узла для однорангового подключения для игр, конструктор должен находиться в блоке try/catch. Если возникает исключение, приложение может уведомить пользователя и запросить новое имя узла.

Добавление кода для проверки строки имени узла от пользователя

// Define some variables at the class level.
Windows::Networking::HostName^ remoteHost;

bool isHostnameFromUser = false;
bool isHostnameValid = false;

///...

// If the value of 'remoteHostname' is set by the user in a control as input 
// and is therefore untrusted input and could contain errors. 
// If we can't create a valid hostname, we notify the user in statusText 
// about the incorrect input.

String ^hostString = remoteHostname;

try 
{
    remoteHost = ref new Windows::Networking:Host(hostString);
    isHostnameValid = true;
}
catch (InvalidArgumentException ^ex)
{
    statusText->Text = "You entered a bad hostname, please re-enter a valid hostname.";
    return;
}

isHostnameFromUser = true;

// ... Continue with code to execute with a valid hostname.

Пространство имен Windows.Networking.Sockets имеет удобные вспомогательные методы и перечисления для обработки ошибок при использовании сокетов. Это может быть полезно для обработки определенных сетевых исключений в приложении по-разному.

Ошибка, обнаруженная при операции DatagramSocket, StreamSocket или StreamSocketListener, приводит к возникновению исключения. Причина исключения — это значение ошибки, представленное как значение HRESULT . Метод SocketError.GetStatus используется для преобразования сетевой ошибки из операции сокета в значение перечисления SocketErrorStatus. Большинство значений перечисления SocketErrorStatus соответствуют ошибке, возвращаемой операцией собственных сокетов Windows. Приложение может фильтровать определенные значения перечисления SocketErrorStatus , чтобы изменить поведение приложения в зависимости от причины исключения.

Для ошибок проверки параметров приложение также может использовать HRESULT из исключения, чтобы узнать более подробные сведения об ошибке, вызвавшей исключение. Возможные значения HRESULT перечислены в файле заголовка Winerror.h . Для многих ошибок при проверке параметров HRESULT возвращает значение E\_INVALIDARG.

Добавление кода для обработки исключений при попытке установить подключение сокета потока

using namespace Windows::Networking;
using namespace Windows::Networking::Sockets;
    
    // Define some more variables at the class level.

    bool isSocketConnected = false
    bool retrySocketConnect = false;

    // The number of times we have tried to connect the socket.
    unsigned int retryConnectCount = 0;

    // The maximum number of times to retry a connect operation.
    unsigned int maxRetryConnectCount = 5; 
    ///...

    // We pass in a valid remoteHost and serviceName parameter.
    // The hostname can contain a name or an IP address.
    // The servicename can contain a string or a TCP port number.

    StreamSocket ^ socket = ref new StreamSocket();
    SocketErrorStatus errorStatus; 
    HResult hr;

    // Save the socket, so any subsequent steps can use it.
    CoreApplication::Properties->Insert("clientSocket", socket);

    // Connect to the remote server. 
    create_task(socket->ConnectAsync(
            remoteHost,
            serviceName,
            SocketProtectionLevel::PlainSocket)).then([this] (task<void> previousTask)
    {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.get();

            isSocketConnected = true;
            // Mark the socket as connected. We do not really care about the value of the property, but the mere 
            // existence of  it means that we are connected.
            CoreApplication::Properties->Insert("connected", nullptr);
        }
        catch (Exception^ ex)
        {
            hr = ex.HResult;
            errorStatus = SocketStatus::GetStatus(hr); 
            if (errorStatus != Unknown)
            {
                                                                switch (errorStatus) 
                   {
                    case HostNotFound:
                        // If the hostname is from the user, this may indicate a bad input.
                        // Set a flag to ask the user to re-enter the hostname.
                        isHostnameValid = false;
                        return;
                        break;
                    case ConnectionRefused:
                        // The server might be temporarily busy.
                        retrySocketConnect = true;
                        return;
                        break; 
                    case NetworkIsUnreachable: 
                        // This could be a connectivity issue.
                        retrySocketConnect = true;
                        break;
                    case UnreachableHost: 
                        // This could be a connectivity issue.
                        retrySocketConnect = true;
                        break;
                    case NetworkIsDown: 
                        // This could be a connectivity issue.
                        retrySocketConnect = true;
                        break;
                    // Handle other errors. 
                    default: 
                        // The connection failed and no options are available.
                        // Try to use cached data if it is available. 
                        // You may want to tell the user that the connect failed.
                        break;
                }
                }
                else 
                {
                    // Received an Hresult that is not mapped to an enum.
                    // This could be a connectivity issue.
                    retrySocketConnect = true;
                }
            }
        });
    }

Исключения в Windows.Web.Http

Конструктор класса Windows::Foundation::Uri, используемый с Windows::Web::Http::HttpClient, может вызвать исключение, если переданная строка не является допустимым универсальным кодом ресурса (содержит символы, которые не разрешены в URI). В C++не существует метода для анализа строки в URI. Если приложение получает входные данные от пользователя для Windows::Foundation::Uri, конструктор должен находиться в блоке try/catch. Если возникает исключение, приложение может уведомить пользователя и запросить новый универсальный код ресурса (URI).

Приложение также должно проверка, что схема в URI — HTTP или HTTPS, так как это единственные схемы, поддерживаемые Windows::Web::HttpClient.

Добавление кода для проверки строки для URI пользователя

    // Define some variables at the class level.
    Windows::Foundation::Uri^ resourceUri;

    bool isUriFromUser = false;
    bool isUriValid = false;

    ///...

    // If the value of 'inputUri' is set by the user in a control as input 
    // and is therefore untrusted input and could contain errors. 
    // If we can't create a valid hostname, we notify the user in statusText 
    // about the incorrect input.

    String ^uriString = inputUri;

    try 
    {
        isUriValid = false;
        resourceUri = ref new Windows::Foundation:Uri(uriString);

        if (resourceUri->SchemeName != "http" && resourceUri->SchemeName != "https")
        {
            statusText->Text = "Only 'http' and 'https' schemes supported. Please re-enter URI";
            return;
        }
        isUriValid = true;
    }
    catch (InvalidArgumentException ^ex)
    {
        statusText->Text = "You entered a bad URI, please re-enter Uri to continue.";
        return;
    }

    isUriFromUser = true;


    // ... Continue with code to execute with a valid URI.

Пространство имен Windows::Web::Http не имеет удобной функции. Таким образом, приложению, использующим HttpClient и другим классам в этом пространстве имен, необходимо использовать значение HRESULT.

В приложениях, использующих C++, функция Platform::Exception представляет ошибку во время выполнения приложения при возникновении исключения. Свойство Platform::Exception::HResult возвращает hrESULT, назначенное конкретному исключению. Свойство Platform::Exception::Message возвращает указанную системой строку, связанную со значением HRESULT . Возможные значения HRESULT перечислены в файле заголовка Winerror.h . Приложение может фильтровать определенные значения HRESULT , чтобы изменить поведение приложения в зависимости от причины исключения.

Для многих ошибок при проверке параметров HRESULT возвращает значение E\_INVALIDARG. Для некоторых недопустимых вызовов методов возвращаемым HRESULT является E_ILLEGAL_METHOD_CALL.

Добавление кода для обработки исключений при попытке использовать HttpClient для подключения к HTTP-серверу

using namespace Windows::Foundation;
using namespace Windows::Web::Http;
    
    // Define some more variables at the class level.

    bool isHttpClientConnected = false
    bool retryHttpClient = false;

    // The number of times we have tried to connect the socket
    unsigned int retryConnectCount = 0;

    // The maximum number of times to retry a connect operation.
    unsigned int maxRetryConnectCount = 5; 
    ///...

    // We pass in a valid resourceUri parameter.
    // The URI must contain a scheme and a name or an IP address.

    HttpClient ^ httpClient = ref new HttpClient();
    HResult hr;

    // Save the httpClient, so any subsequent steps can use it.
    CoreApplication::Properties->Insert("httpClient", httpClient);

    // Send a GET request to the HTTP server. 
    create_task(httpClient->GetAsync(resourceUri)).then([this] (task<void> previousTask)
    {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.get();

            isHttpClientConnected = true;
            // Mark the HttClient as connected. We do not really care about the value of the property, but the mere 
            // existence of  it means that we are connected.
            CoreApplication::Properties->Insert("connected", nullptr);
        }
        catch (Exception^ ex)
        {
            hr = ex.HResult;
                                                switch (errorStatus) 
               {
                case WININET_E_NAME_NOT_RESOLVED:
                    // If the Uri is from the user, this may indicate a bad input.
                    // Set a flag to ask user to re-enter the Uri.
                    isUriValid = false;
                    return;
                    break;
                case WININET_E_CANNOT_CONNECT:
                    // The server might be temporarily busy.
                    retryHttpClientConnect = true;
                    return;
                    break; 
                case WININET_E_CONNECTION_ABORTED: 
                    // This could be a connectivity issue.
                    retryHttpClientConnect = true;
                    break;
                case WININET_E_CONNECTION_RESET: 
                    // This could be a connectivity issue.
                    retryHttpClientConnect = true;
                    break;
                case INET_E_RESOURCE_NOT_FOUND: 
                    // The server cannot locate the resource specified in the uri.
                    // If the Uri is from user, this may indicate a bad input.
                    // Set a flag to ask the user to re-enter the Uri
                    isUriValid = false;
                    return;
                    break;
                // Handle other errors. 
                default: 
                    // The connection failed and no options are available.
                    // Try to use cached data if it is available. 
                    // You may want to tell the user that the connect failed.
                    break;
            }
            else 
            {
                // Received an Hresult that is not mapped to an enum.
                // This could be a connectivity issue.
                retrySocketConnect = true;
            }
        }
    });

Другие ресурсы

Ссылка

Примеры приложений