Reti per i giochi

Scopri come sviluppare e incorporare funzionalità di rete nel tuo gioco DirectX.

Concetti generali

Un'ampia gamma di funzionalità di rete può essere usata nel tuo gioco DirectX, indipendentemente dal fatto che si tratti di un semplice gioco autonomo per più giocatori. L'uso più semplice della rete sarebbe quello di archiviare i nomi utente e i punteggi di gioco in un server di rete centrale.

Le API di rete sono necessarie nei giochi multi-giocatore che usano il modello di infrastruttura (client-server o peer-to-peer) e anche da giochi ad hoc (peer-to-peer locali). Per i giochi multi-giocatore basati su server, un server di gioco centrale gestisce in genere la maggior parte delle operazioni del gioco e l'app del gioco client viene usata per l'input, la grafica di visualizzazione, la riproduzione di audio e altre funzionalità. La velocità e la latenza dei trasferimenti di rete sono un problema per un'esperienza di gioco soddisfacente.

Per i giochi peer-to-peer, l'app di ogni giocatore gestisce l'input e la grafica. Nella maggior parte dei casi, i giocatori del gioco si trovano in prossimità in modo che la latenza di rete sia inferiore, ma è comunque un problema. Come individuare i peer e stabilire una connessione diventa un problema.

Per i giochi a giocatore singolo, un servizio o un server Web centrale viene spesso usato per archiviare nomi utente, punteggi di gioco e altre informazioni varie. In questi giochi, la velocità e la latenza dei trasferimenti di rete sono meno preoccupanti perché non influisce direttamente sul funzionamento del gioco.

Le condizioni di rete possono cambiare in qualsiasi momento, quindi qualsiasi gioco che usa le API di rete deve gestire le eccezioni di rete che possono verificarsi. Per altre informazioni sulla gestione delle eccezioni di rete, vedere Nozioni di base sulla rete.

I firewall e i proxy Web sono comuni e possono influire sulla possibilità di usare le funzionalità di rete. Un gioco che usa la rete deve essere preparato per gestire correttamente firewall e proxy.

Per i dispositivi mobili, è importante monitorare le risorse di rete disponibili e comportarsi di conseguenza nei casi in cui si utilizzano reti a consumo e i costi di roaming o dati possono essere significativi.

L'isolamento della rete fa parte del modello di sicurezza delle app usato da Windows. Windows individua attivamente i limiti di rete e applica restrizioni di accesso alla rete per l'isolamento della rete. Le app devono dichiarare funzionalità di isolamento della rete per definire l'ambito dell'accesso alla rete. Senza dichiarare queste funzionalità, l'app non avrà accesso alle risorse di rete. Per altre informazioni su come Windows applica l'isolamento di rete per le app, vedere Come configurare le funzionalità di isolamento della rete.

Considerazioni relative alla progettazione

Nei giochi DirectX è possibile usare un'ampia gamma di API di rete. È quindi importante scegliere l'API corretta. Windows supporta un'ampia gamma di API di rete che l'app può usare per comunicare con altri computer e dispositivi tramite Internet o reti private. Il primo passaggio consiste nel determinare le funzionalità di rete necessarie per l'app.

Si tratta delle API di rete più diffuse per i giochi.

  • TCP e socket: fornisce una connessione affidabile. Usare TCP per le funzioni di gioco che non necessitano di sicurezza. TCP consente al server di ridimensionare facilmente, quindi viene comunemente usato nei giochi che usano il modello di infrastruttura (client-server o peer-to-peer Internet). TCP può essere usato anche da giochi ad hoc (peer-to-peer locale) su Wi-Fi Direct e Bluetooth. TCP viene comunemente usato per lo spostamento degli oggetti del gioco, l'interazione tra caratteri, la chat di testo e altre operazioni. La classe StreamSocket fornisce un socket TCP che può essere usato nei giochi di Microsoft Store. La classe StreamSocket viene usata con le classi correlate nello spazio dei nomi Windows::Networking::Sockets.
  • TCP e socket che usano SSL: fornisce una connessione affidabile che impedisce l'intercettazione. Usare le connessioni TCP con SSL per le operazioni di gioco che richiedono sicurezza. La crittografia e il sovraccarico di SSL comportano un costo in termini di latenza e prestazioni, quindi viene usato solo quando è necessaria la sicurezza. TCP con SSL viene comunemente usato per l'accesso, l'acquisto e il trading di asset, la creazione e la gestione dei personaggi del gioco. La classe StreamSocket fornisce un socket TCP che supporta SSL.
  • UDP e socket: fornisce trasferimenti di rete inaffidabili con un overhead ridotto. UDP viene usato per le operazioni di gioco che richiedono bassa latenza e possono tollerare una perdita di pacchetti. Questo viene spesso usato per giochi di combattimento, tiro e tracciatori, audio di rete e chat vocale. La classe DatagramSocket fornisce un socket UDP che può essere usato nei giochi di Microsoft Store. La classe DatagramSocket viene usata con le classi correlate nello spazio dei nomi Windows::Networking::Sockets.
  • Client HTTP: fornisce una connessione affidabile ai server HTTP. Lo scenario di rete più comune consiste nell'accedere a un sito Web per recuperare o archiviare informazioni. Un semplice esempio è un gioco che usa un sito Web per archiviare informazioni utente e punteggi di gioco. Se usato con SSL per la sicurezza, un client HTTP può essere usato per l'accesso, l'acquisto, gli asset di trading, la creazione di personaggi di gioco e la gestione. La classe HttpClient fornisce un'API client HTTP moderna da usare nei giochi di Microsoft Store. La classe HttpClient viene usata con le classi correlate nello spazio dei nomi Windows::Web::Http.

Gestione delle eccezioni di rete nel gioco DirectX

Quando si verifica un'eccezione di rete nel gioco DirectX, questo indica un problema o un errore significativo. Quando si usano le API di rete le eccezioni possono verificarsi per molti motivi. Spesso, l'eccezione può derivare da modifiche alla connettività di rete o ad altri problemi di rete con l'host remoto o il server.

Alcune cause di eccezioni quando si usano le API di rete includono quanto segue:

  • L'input dell'utente per un nome host o un URI contiene errori e non è valido.
  • Errori di risoluzione dei nomi durante la ricerca di un nome host o di un URi.
  • Perdita o modifica della connettività di rete.
  • Errori di connessione di rete tramite socket o API client HTTP.
  • Errori del server di rete o dell'endpoint remoto.
  • Errori di rete vari.

Le eccezioni da errori di rete (ad esempio, perdita o modifica della connettività, errori di connessione ed errori del server) possono verificarsi in qualsiasi momento. Questi errori generano eccezioni. Se un'eccezione non viene gestita dall'app, l'intera app può essere terminata dal runtime.

È necessario scrivere il codice per gestire le eccezioni quando si richiama la maggior parte dei metodi di rete asincroni. In alcuni casi, quando si verifica un'eccezione, è possibile ritentare un metodo di rete per risolvere il problema. In altri casi, l'app potrebbe dover continuare senza connettività di rete usando i dati memorizzati nella cache in precedenza.

Le app UWP (Universal Windows Platform) generano in genere una singola eccezione. Il gestore eccezioni può recuperare informazioni più dettagliate sulla causa dell'eccezione per comprendere meglio l'errore e prendere decisioni appropriate.

Quando si verifica un'eccezione in un gioco DirectX che è un'app UWP, è possibile recuperare il valore HRESULT relativamente alla causa dell'errore. Il file Winerror.h include un elenco molto lungo di possibili valori HRESULT che include gli errori di rete.

Le API di rete supportano diversi metodi per recuperare informazioni dettagliate sulla causa di un'eccezione.

  • Metodo per recuperare il valore HRESULT dell'errore che ha causato l'eccezione. L'elenco possibile di valori HRESULT potenziali è grande e non specificato. Il valore HRESULT può essere recuperato quando si usa una qualunque delle API di rete.
  • Metodo helper che converte il valore HRESULT in un valore di enumerazione. L'elenco dei possibili valori di enumerazione è specificato e relativamente piccolo. Un metodo helper è disponibile per le classi socket in Windows::Networking::Sockets.

Eccezioni in Windows.Networking.Sockets

Il costruttore relativo alla classe HostName utilizzata con socket può generare un'eccezione se la stringa trasferita non è un nome host valido (contiene caratteri non consentiti in un nome host). Se un'app ottiene l'input dall'utente per HostName per una connessione peer per il gioco, il costruttore deve essere in un blocco try/catch. Se viene generata un'eccezione, l'app può inviare una notifica all'utente e richiedere un nuovo nome host.

Aggiungere il codice per convalidare una stringa per un nome host proveniente dall'utente

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

Lo spazio dei nomi Windows.Networking.Sockets include pratici metodi helper ed enumerazioni per la gestione degli errori quando si usano i socket. Questo può essere utile per gestire eccezioni di rete specifiche in modo diverso nell'app.

Si è verificato un errore nell'operazione DatagramSocket, StreamSocket o StreamSocketListener ed esso genera un’eccezione. La causa dell'eccezione è un valore di errore rappresentato come valore HRESULT. Il metodo SocketError.GetStatus viene utilizzato per convertire un errore di rete da un'operazione socket a un valore di enumerazione SocketErrorStatus La maggior parte dei valori di enumerazione SocketErrorStatus corrisponde a un errore restituito dall'operazione socket di Windows nativa. L'app può filtrare i valori di enumerazione specifici di SocketErrorStatus allo scopo di modificare il comportamento dell'app a seconda della causa dell'eccezione.

Per gli errori di convalida dei parametri, un'app può anche usare HRESULT dall'eccezione per ottenere informazioni più dettagliate sull'errore che ha causato l'eccezione. I possibili valori HRESULT sono elencati nel file di intestazione Winerror.h . Per gran parte degli errori di convalida dei parametri, il valore HRESULT restituito è E\_INVALIDARG.

Aggiungere codice per gestire le eccezioni quando si tenta di stabilire una connessione socket di flusso

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

Eccezioni in Windows.Web.Http

Il costruttore per la classe Windows::Foundation::Uri usato con Windows::Web::Http::HttpClient può generare un'eccezione se la stringa passata non è un URI valido (contiene caratteri non consentiti in un URI). In C++, non esiste alcun metodo per provare e analizzare una stringa in un URI. Se un'app ottiene l'input dall'utente per Windows::Foundation::Uri, il costruttore deve trovarsi in un blocco try/catch. Se viene generata un'eccezione, l'app può inviare una notifica all'utente e richiedere un nuovo URI.

L'app deve anche verificare che lo schema nell'URI sia HTTP o HTTPS, poiché questi sono gli unici schemi supportati da Windows::Web::Http::HttpClient.

Aggiungere il codice per convalidare una stringa per un URI dell'utente

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

Lo spazio dei nomi Windows::Web::Http non dispone di una funzione utile. Pertanto, un'app che usa HttpClient e altre classi in questo spazio dei nomi deve usare il valore HRESULT.

Nelle app che usano C++/CX, Platform::Exception rappresenta un errore che si verifica durante l'esecuzione di un'app in caso di eccezione. La proprietà Platform::Exception::HResult restituisce il valore HRESULT assegnato alla specifica eccezione. La proprietà Platform::Exception::Message restituisce la stringa fornita dal sistema associata al valore HRESULT. I possibili valori HRESULT sono elencati nel file di intestazione Winerror.h . È possibile applicare filtri per specifici valori di HRESULT allo scopo di modificare il comportamento dell'app a seconda della causa dell'eccezione.

Per gran parte degli errori di convalida dei parametri, il valore HRESULT restituito è E\_INVALIDARG. Per alcune chiamate al metodo non valide, il valore HRESULT restituito è E_ILLEGAL_METHOD_CALL.

Aggiungere codice per gestire le eccezioni quando si tenta di usare HttpClient per connettersi a un server 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;
            }
        }
    });

Altre risorse

Riferimento

App di esempio