Partager via


Mise en réseau pour les jeux

Découvrez comment développer et incorporer des fonctionnalités de mise en réseau dans votre jeu DirectX.

Concepts en un clin d’œil

Une variété de fonctionnalités de mise en réseau peut être utilisée dans votre jeu DirectX, qu’il s’agisse d’un jeu autonome simple pour des jeux massivement multi-joueurs. L’utilisation la plus simple de la mise en réseau serait de stocker les noms d’utilisateurs et les scores de jeu sur un serveur réseau central.

Les API réseau sont nécessaires dans les jeux multijoueurs qui utilisent le modèle d'infrastructure (client-serveur ou pair-à-pair Internet) et également dans les jeux ad hoc (pair-à-pair local). Pour les jeux multi-joueurs basés sur le serveur, un serveur de jeu central gère généralement la plupart des opérations de jeu et l’application de jeu client est utilisée pour l’entrée, l’affichage de graphiques, la lecture audio et d’autres fonctionnalités. La vitesse et la latence des transferts réseau sont un problème pour une expérience de jeu satisfaisante.

Pour les jeux d’égal à égal, l’application de chaque joueur gère l’entrée et les graphiques. Dans la plupart des cas, les joueurs de jeu se trouvent à proximité, de sorte que la latence du réseau doit être inférieure, mais est toujours une préoccupation. Comment détecter des homologues et établir une connexion devient un problème.

Pour les jeux à joueur unique, un serveur web central ou un service est souvent utilisé pour stocker des noms d’utilisateurs, des scores de jeu et d’autres informations diverses. Dans ces jeux, la vitesse et la latence des transferts réseau sont moins préoccupantes, car elles n’affectent pas directement l’opération de jeu.

Les conditions réseau peuvent changer à tout moment, de sorte que tout jeu qui utilise des API réseau doit gérer les exceptions réseau qui peuvent se produire. Pour en savoir plus sur la gestion des exceptions réseau, consultez Concepts de base de la mise en réseau.

Les pare-feu et les proxys web sont courants et peuvent affecter la possibilité d’utiliser des fonctionnalités réseau. Un jeu qui utilise la mise en réseau doit être prêt à gérer correctement les pare-feu et les proxys.

Pour les appareils mobiles, il est important de surveiller les ressources réseau disponibles et de se comporter en conséquence lorsque sur les réseaux mesurés où les coûts d’itinérance ou de données peuvent être significatifs.

L’isolation réseau fait partie du modèle de sécurité des applications utilisé par Windows. Windows découvre activement les limites du réseau et applique des restrictions d’accès réseau pour l’isolation du réseau. Les applications doivent déclarer des fonctionnalités d’isolation réseau pour définir l’étendue de l’accès réseau. Sans déclarer ces fonctionnalités, votre application n’aura pas accès aux ressources réseau. Pour en savoir plus sur la façon dont Windows applique l’isolation réseau pour les applications, consultez Comment configurer les fonctionnalités d’isolation réseau.

Considérations relatives à la conception

Une variété d’API réseau peut être utilisée dans les jeux DirectX. Il est donc important de choisir l’API appropriée. Windows prend en charge une variété d’API réseau que votre application peut utiliser pour communiquer avec d’autres ordinateurs et appareils via Internet ou des réseaux privés. Votre première étape consiste à déterminer les fonctionnalités réseau dont votre application a besoin.

Il s’agit des API réseau les plus populaires pour les jeux.

  • TCP et sockets : fournit une connexion fiable. Utilisez TCP pour les opérations de jeu qui n’ont pas besoin de sécurité. TCP permet au serveur de mettre à l’échelle facilement, de sorte qu’il est couramment utilisé dans les jeux qui utilisent le modèle d’infrastructure (serveur client ou pair à pair Internet). TCP peut également être utilisé par des jeux ad hoc (peer-to-peer) locaux via Wi-Fi Direct et Bluetooth. TCP est couramment utilisé pour le déplacement d’objets de jeu, l’interaction des caractères, la conversation de texte et d’autres opérations. La classe StreamSocket fournit un socket TCP qui peut être utilisé dans les jeux du Microsoft Store. La classe StreamSocket est utilisée avec les classes associées dans l’espace de noms Windows ::Networking ::Sockets .
  • TCP et sockets utilisant SSL - fournit une connexion fiable qui empêche l'écoute clandestine. Utilisez des connexions TCP avec SSL pour les opérations de jeu nécessitant une sécurité. Le chiffrement et la surcharge de SSL ajoutent un coût en latence et en performances. Il est donc utilisé uniquement lorsque la sécurité est nécessaire. TCP avec SSL est couramment utilisé pour la connexion, l’achat et les actifs commerciaux, la création et la gestion des personnages de jeu. La classe StreamSocket fournit un socket TCP qui prend en charge SSL.
  • UDP et sockets : fournit des transferts réseau non fiables avec une faible surcharge. UDP est utilisé pour les opérations de jeu qui nécessitent une faible latence et peut tolérer une perte de paquets. Ceci est souvent utilisé pour les jeux de combat, les jeux de tir et les traceurs, l’audio réseau et le chat vocal. La classe DatagramSocket fournit un socket UDP qui peut être utilisé dans les jeux du Microsoft Store. La classe DatagramSocket est utilisée avec les classes associées dans l’espace de noms Windows ::Networking ::Sockets .
  • Client HTTP : fournit une connexion fiable aux serveurs HTTP. Le scénario de mise en réseau le plus courant consiste à accéder à un site web pour récupérer ou stocker des informations. Un exemple simple serait un jeu qui utilise un site web pour stocker des informations utilisateur et des scores de jeu. Lorsqu’il est utilisé avec SSL pour la sécurité, un client HTTP peut être utilisé pour la connexion, l’achat, les actifs commerciaux, la création et la gestion des personnages de jeu. La classe HttpClient fournit une API cliente HTTP moderne à utiliser dans les jeux du Microsoft Store. La classe HttpClient est utilisée avec les classes associées dans l’espace de noms Windows ::Web ::Http .

Gestion des exceptions réseau dans votre jeu DirectX

Lorsqu’une exception réseau se produit dans votre jeu DirectX, cela indique un problème ou un échec significatif. Les exceptions peuvent se produire pour de nombreuses raisons lors de l’utilisation d’API réseau. Souvent, l’exception peut provenir de modifications apportées à la connectivité réseau ou à d’autres problèmes réseau avec l’hôte ou le serveur distant.

Voici quelques causes d’exceptions lors de l’utilisation d’API réseau :

  • L’entrée de l’utilisateur pour un nom d’hôte ou un URI contient des erreurs et n’est pas valide.
  • Échecs de résolution de noms lors de la recherche d’un nom d’hôte ou d’une URI.
  • Perte ou modification de la connectivité réseau.
  • Échecs de connexion réseau avec les sockets ou les API clients HTTP.
  • Erreurs de serveur réseau ou de point de terminaison distant.
  • Erreurs de mise en réseau diverses.

Les exceptions des erreurs réseau (par exemple, perte ou modification de la connectivité, des défaillances de connexion et des défaillances de serveur) peuvent se produire à tout moment. Ces erreurs entraînent la levée d’exceptions. Si une exception n’est pas gérée par votre application, elle peut mettre fin à l’ensemble de votre application par le runtime.

Vous devez écrire du code pour gérer les exceptions lorsque vous appelez la plupart des méthodes réseau asynchrones. Parfois, lorsqu’une exception se produit, une méthode réseau peut être retentée comme moyen de résoudre le problème. Dans d’autres cas, votre application peut avoir besoin de planifier la poursuite sans connectivité réseau à l’aide de données précédemment mises en cache.

Les applications de plateforme Windows universelle (UWP) lèvent généralement une exception unique. Votre gestionnaire d’exceptions peut récupérer des informations plus détaillées sur la cause de l’exception pour mieux comprendre l’échec et prendre les décisions appropriées.

Lorsqu’une exception se produit dans un jeu DirectX qui est une application UWP, la valeur HRESULT pour la cause de l’erreur peut être récupérée. Le fichier include Winerror.h contient une grande liste de valeurs possibles HRESULT qui incluent des erreurs réseau.

Les API réseau prennent en charge différentes méthodes pour récupérer ces informations détaillées sur la cause d’une exception.

  • Méthode permettant de récupérer la valeur HRESULT de l’erreur qui a provoqué l’exception. La liste possible de valeurs HRESULT potentielles est importante et non spécifiée. La valeur HRESULT peut être récupérée lors de l’utilisation d’une des API réseau.
  • Méthode d’assistance qui convertit la valeur HRESULT en valeur d’énumération. La liste des valeurs d’énumération possibles est spécifiée et relativement petite. Une méthode d’assistance est disponible pour les classes de socket dans Windows ::Networking ::Sockets.

Exceptions dans Windows.Networking.Sockets

Le constructeur de la classe HostName utilisée avec des sockets peut lever une exception si la chaîne passée n’est pas un nom d’hôte valide (contient des caractères qui ne sont pas autorisés dans un nom d’hôte). Si une application obtient des données de l'utilisateur pour l'HostName pour une connexion entre pairs de jeu, le constructeur doit se trouver dans un bloc try/catch. Si une exception est levée, l’application peut avertir l’utilisateur et demander un nouveau nom d’hôte.

Ajouter du code pour valider une chaîne pour un nom d’hôte de l’utilisateur

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

L’espace de noms Windows.Networking.Sockets a des méthodes d’assistance et des énumérations pratiques pour gérer les erreurs lors de l’utilisation de sockets. Cela peut être utile pour gérer des exceptions réseau spécifiques différemment dans votre application.

Une erreur rencontrée sur DatagramSocket, StreamSocket, ou StreamSocketListener entraîne la levée d’une exception. La cause de l’exception est une valeur d’erreur représentée sous forme de valeur HRESULT . La méthode SocketError.GetStatus est utilisée pour convertir une erreur réseau d’une opération de socket en valeur d’énumération SocketErrorStatus . La plupart des valeurs d’énumération SocketErrorStatus correspondent à une erreur retournée par l’opération de sockets Windows native. Une application peut filtrer sur des valeurs d’énumération SocketErrorStatus spécifiques pour modifier le comportement de l’application en fonction de la cause de l’exception.

Pour les erreurs de validation de paramètre, une application peut également utiliser HRESULT à partir de l’exception pour en savoir plus sur l’erreur qui a provoqué l’exception. Les valeurs HRESULT possibles sont répertoriées dans le fichier d’en-tête Winerror.h. Pour la plupart des erreurs de validation de paramètre, le HRESULT retourné est E_INVALIDARG.

Ajouter du code pour gérer les exceptions lors de la tentative d’établir une connexion de socket de flux

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

Exceptions dans Windows.Web.Http

Le constructeur de la classe Windows ::Foundation ::Uri utilisée avec Windows ::Web ::HttpClient peut lever une exception si la chaîne passée n’est pas un URI valide (contient des caractères qui ne sont pas autorisés dans un URI). En C++, il n’existe aucune méthode pour essayer d’analyser une chaîne à un URI. Si une application reçoit une entrée de l'utilisateur pour l'Windows::Foundation::Uri, le constructeur doit se trouver dans un bloc try/catch. Si une exception est levée, l’application peut avertir l’utilisateur et demander un nouvel URI.

Votre application doit également vérifier que le schéma dans l’URI est HTTP ou HTTPS, car il s’agit des seuls schémas pris en charge par Windows ::Web ::Http ::HttpClient.

Ajouter du code pour valider une chaîne pour un URI de l’utilisateur

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

L’espace de noms Windows ::Web ::Http ne dispose pas d’une fonction pratique. Par conséquent, une application utilisant HttpClient et d’autres classes dans cet espace de noms doit utiliser la valeur HRESULT .

Dans les applications utilisant C++, l’exception Platform ::Exception représente une erreur lors de l’exécution de l’application lorsqu’une exception se produit. La propriété Platform::Exception::HResult retourne le HRESULT attribué à l’exception spécifique. La propriété Platform::Exception::Message retourne la chaîne associée à la valeur HRESULT, fournie par le système. Les valeurs HRESULT possibles sont répertoriées dans le fichier d’en-tête Winerror.h. Une application peut filtrer sur des valeurs HRESULT spécifiques pour modifier le comportement de l’application en fonction de la cause de l’exception.

Pour la plupart des erreurs de validation de paramètre, le HRESULT retourné est E_INVALIDARG. Pour certains appels de méthode illégaux, le HRESULT retourné est E_ILLEGAL_METHOD_CALL.

Ajouter du code pour gérer les exceptions lors de la tentative d’utilisation de HttpClient pour se connecter à un serveur 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;
            }
        }
    });

Autres ressources

Référence

Exemples d’applications