Rede para jogos

Saiba como desenvolver e incorporar recursos de rede em seu jogo DirectX.

Visão rápida de conceitos

Uma variedade de recursos de rede pode ser usada em seu jogo DirectX, seja um jogo autônomo simples para jogos massivamente multijogador. O uso mais simples da rede seria armazenar nomes de usuário e pontuações de jogo em um servidor de rede central.

AS APIs de rede são necessárias em jogos multijogadores que usam o modelo de infraestrutura (cliente-servidor ou internet ponto a ponto) e também por jogos ad hoc (ponto a ponto local). Para jogos multijogadores baseados em servidor, um servidor de jogos central geralmente manipula a maioria das operações de jogo e o aplicativo de jogo cliente é usado para entrada, exibição de gráficos, reprodução de áudio e outros recursos. A velocidade e a latência das transferências de rede são uma preocupação para uma experiência de jogo satisfatória.

Para jogos ponto a ponto, o aplicativo de cada jogador manipula a entrada e os elementos gráficos. Na maioria dos casos, os jogadores do jogo estão localizados próximos, de modo que a latência de rede deve ser menor, mas ainda é uma preocupação. Como descobrir pares e estabelecer uma conexão torna-se uma preocupação.

Para jogos de um único jogador, um servidor Web central ou serviço geralmente é usado para armazenar nomes de usuário, pontuações de jogo e outras informações diversas. Nesses jogos, a velocidade e a latência das transferências de rede são menos preocupantes, pois não afetam diretamente a operação do jogo.

As condições de rede podem mudar a qualquer momento, portanto, qualquer jogo que use APIs de rede precisa lidar com exceções de rede que possam ocorrer. Para saber mais sobre como lidar com exceções de rede, consulte noções básicas de rede.

Firewalls e proxies da Web são comuns e podem afetar a capacidade de usar recursos de rede. Um jogo que usa a rede precisa estar preparado para lidar corretamente com firewalls e proxies.

Para dispositivos móveis, é importante monitorar os recursos de rede disponíveis e se comportar adequadamente quando em redes limitadas em que os custos de roaming ou de dados podem ser significativos.

O isolamento de rede faz parte do modelo de segurança do aplicativo usado pelo Windows. O Windows descobre ativamente os limites de rede e impõe restrições de acesso à rede para isolamento de rede. Os aplicativos devem declarar recursos de isolamento de rede para definir o escopo do acesso à rede. Sem declarar esses recursos, seu aplicativo não terá acesso aos recursos de rede. Para saber mais sobre como o Windows impõe o isolamento de rede para aplicativos, consulte Como configurar recursos de isolamento de rede.

Considerações sobre design

Uma variedade de APIs de rede pode ser usada em jogos DirectX. Portanto, é importante escolher a API certa. O Windows dá suporte a uma variedade de APIs de rede que seu aplicativo pode usar para se comunicar com outros computadores e dispositivos pela Internet ou redes privadas. Sua primeira etapa é descobrir quais recursos de rede seu aplicativo precisa.

Essas são as APIs de rede mais populares para jogos.

  • TCP e soquetes – Fornece uma conexão confiável. Use o TCP para operações de jogo que não precisam de segurança. O TCP permite que o servidor seja dimensionado facilmente, portanto, ele é comumente usado em jogos que usam o modelo de infraestrutura (cliente-servidor ou internet ponto a ponto). O TCP também pode ser usado por jogos ad hoc (ponto a ponto local) por Wi-Fi Direct e Bluetooth. O TCP é comumente usado para movimentação de objeto do jogo, interação de caracteres, chat de texto e outras operações. A classe StreamSocket fornece um soquete de rede TCP que pode ser usado nos jogos da Microsoft Store. A classe StreamSocket é usada com classes relacionadas no namespace Windows::Networking::Sockets.
  • TCP e soquetes usando SSL – Fornece uma conexão confiável que impede a escuta. Use conexões TCP com SSL para operações de jogo que precisam de segurança. A criptografia e a sobrecarga do SSL adicionam um custo em latência e desempenho, portanto, ela só é usada quando a segurança é necessária. O TCP com SSL geralmente é usado para logon, compra e negociação de ativos, criação e gerenciamento de caracteres de jogo. A classe StreamSocket fornece um soquete TCP que dá suporte a SSL.
  • UDP e soquetes – Fornece transferências de rede não confiáveis com baixa sobrecarga. O UDP é usado para operações de jogo que exigem baixa latência e podem tolerar alguma perda de pacote. Isso geralmente é usado para jogos de luta, tiro e rastreamentos, áudio de rede e chat de voz. A classe DatagramSocket fornece um soquete de rede UDP que pode ser usado nos jogos da Microsoft Store. A classe DatagramSocket é usada com classes relacionadas no namespace Windows::Networking::Sockets.
  • Cliente HTTP – Fornece uma conexão confiável para servidores HTTP. O cenário de rede mais comum é acessar um site para recuperar ou armazenar informações. Um exemplo simples seria um jogo que usa um site para armazenar informações do usuário e pontuações do jogo. Quando usado com SSL para segurança, um cliente HTTP pode ser usado para logon, compra, negociação de ativos, criação de caracteres de jogo e gerenciamento. A classe HttpClient fornece uma API moderna de cliente HTTP para jogos da Microsoft Store. A classe HttpClient é usada com classes relacionadas no namespace Windows::Web::Http.

Manipulando exceções de rede em seu jogo DirectX

Quando ocorre uma exceção de rede em seu jogo DirectX, isso indica um problema ou falha significativo. Exceções podem ocorrer por muitos motivos ao usar APIs de rede. Geralmente, a exceção pode resultar de alterações na conectividade de rede ou outros problemas de rede com o host ou servidor remoto.

Algumas causas de exceções ao usar APIs de rede incluem o seguinte:

  • A entrada do usuário para um nome de host ou um URI contém erros e não é válida.
  • Falhas nas resoluções de nomes ao procurar um nome de host ou uma interface do usuário.
  • Perda ou alteração na conectividade de rede.
  • Falhas de conexão de rede usando soquetes ou as APIs de cliente HTTP.
  • Erros de servidor de rede ou ponto de extremidade remoto.
  • Erros diversos de rede.

Exceções de erros de rede (por exemplo, perda ou alteração de conectividade, falhas de conexão e falhas de servidor) podem ocorrer a qualquer momento. Esses erros resultam na geração de exceções. Se uma exceção não for tratada pelo seu aplicativo, ela poderá fazer com que todo o aplicativo seja encerrado pelo runtime.

É necessário escrever um código para tratar exceções quando você chama a maioria dos métodos de rede assíncronos. Às vezes, quando ocorre uma exceção, um método de rede pode ser repetido como uma maneira de resolver o problema. Outras vezes, talvez seu aplicativo precise planejar continuar sem conectividade de rede usando dados armazenados em cache anteriormente.

Os aplicativos da Plataforma Universal do Windows (UWP) geralmente geram uma única exceção. Seu manipulador de exceção pode recuperar informações mais detalhadas sobre a causa da exceção para entender melhor a falha e tomar as decisões apropriadas.

Quando ocorre uma exceção em um jogo DirectX que é um aplicativo UWP, o valor HRESULT para a causa do erro pode ser recuperado. O arquivo de inclusão Winerror.h contém uma lista grande de valores HRESULT possíveis, incluindo erros de rede.

As APIs de rede dão suporte a métodos diferentes para recuperar essas informações detalhadas sobre a causa de uma exceção.

  • Um método para recuperar o valor HRESULT do erro que causou a exceção. A lista possível de possíveis valores HRESULT é grande e não especificada. O valor HRESULT pode ser recuperado ao usar qualquer uma das APIs de rede.
  • Um método auxiliar que converte o valor HRESULT em um valor de enumeração. A lista de possíveis valores de enumeração é especificada e relativamente pequena. Um método auxiliar está disponível para as classes de soquete no Windows::Networking::Sockets.

Exceções em Windows.Networking.Sockets

O construtor da classe HostName usada com soquetes pode gerar uma exceção se a cadeia de caracteres passada não for um nome de host válido (contém caracteres que não são permitidos em um nome de host). Se um aplicativo receber a entrada do usuário para o HostName para uma conexão par para jogos, o construtor deverá estar em um bloco try/catch. Se uma exceção for lançada, o aplicativo poderá notificar o usuário e solicitar um novo nome de host.

Adicionar código para validar uma cadeia de caracteres para um nome de host do usuário

// 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.

O namespace Windows.Networking.Sockets tem métodos auxiliares convenientes e enumerações para lidar com erros ao usar soquetes. Eles são úteis para resolver exceções de rede específicas de uma outra forma em seu aplicativo.

Um erro encontrado em operações DatagramSocket, StreamSocket, ou StreamSocketListener resulta em uma exceção que está sendo gerada. A causa da exceção é um valor de erro representado como um valor HRESULT. O método SocketError.GetStatus é usado para converter um erro de rede de uma operação de soquete em um valor de enumeração SocketErrorStatus. A maioria dos valores de enumeração SocketErrorStatus correspondem a um erro retornado pela operação nativa do Windows Sockets. Um aplicativo pode filtrar em valores de enumeração de SocketErrorStatus específicos para modificar o comportamento do aplicativo, dependendo da causa da exceção.

Para erros de validação de parâmetro, um aplicativo também pode usar o HRESULT da exceção para saber mais informações detalhadas sobre o erro que causou a exceção. Os valores possíveis de HRESULT estão listados no arquivo de cabeçalho Winerror.h. Para a maioria dos erros de validação de parâmetro, o HRESULT retornado é E_INVALIDARG.

Adicionar código para lidar com exceções ao tentar fazer uma conexão de soquete de fluxo

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;
                }
            }
        });
    }

Exceções em Windows.Web.Http

O construtor da classe Windows::Foundation::Uri usada com Windows::Web::Http::HttpClient poderá gerar uma exceção se a cadeia de caracteres aprovada não for um URI válido (contém caracteres que não são permitidos em um URI). No C++, não há nenhum método para tentar analisar uma cadeia de caracteres para um URI. Se um aplicativo receber entrada do usuário para o Windows.Foundation.Uri, o construtor deverá estar em um bloco try/catch. Se uma exceção for gerada, o aplicativo poderá notificar o usuário e solicitar um novo URI.

Seu aplicativo também deve verificar se o esquema no URI é HTTP ou HTTPS, pois esses são os únicos esquemas compatíveis com o Windows::Web::Http::HttpClient.

Adicionar código para validar uma cadeia de caracteres para um URI do usuário

    // 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.

O namespace Windows::Web::Http não tem uma função de conveniência. Portanto, o aplicativo que usa HttpClient e outras classes nesse namespace precisa usar o valor HRESULT.

Em aplicativos que usam C++, o Platform::Exception representa um erro durante a execução do aplicativo quando ocorre uma exceção. A propriedade Platform::Exception::HResult retorna o HRESULT atribuído à exceção específica. A propriedade Platform::Exception::Message retorna a cadeia de caracteres fornecida pelo sistema que está associada ao valor de HRESULT. Os valores possíveis de HRESULT estão listados no arquivo de cabeçalho Winerror.h. Um aplicativo pode filtrar valores HRESULT específicos para modificar o comportamento do aplicativo, dependendo da causa da exceção.

Para a maioria dos erros de validação de parâmetro, o HRESULT retornado é E_INVALIDARG. Para algumas chamadas de método ilícitas, o HRESULT retornado é E_ILLEGAL_METHOD_CALL.

Adicionar código para lidar com exceções ao tentar usar httpClient para se conectar a um servidor 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;
            }
        }
    });

Outros recursos

Referência

Aplicativos de exemplo