Mise en réseau pour les jeux

Apprenez à développer et à incorporer des fonctionnalités 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 simple jeu indépendant ou de jeux massivement multijoueur. 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 multijoueur qui utilisent le modèle d’infrastructure (serveur client ou Internet pair à pair) et également par des jeux ad hoc (pair à pair local). Pour les jeux multijoueur basés sur le serveur, un serveur de jeu central gère normalement la plupart des opérations du jeu et l’application du jeu côté 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 en pair à pair, 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 reste toujours un facteur de préoccupation. Comment détecter des pairs et établir une connexion devient un problème.

Pour les jeux en mode solo, 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 du 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 lorsqu’ils se trouvent 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 de celui-ci. 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.

Remarques 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 via réseaux privés. Votre première étape consiste à déterminer les fonctionnalités réseau dont votre application a besoin.

Ce sont les 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 (pair à pair local) sur 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 utilisable dans les jeux de 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 qui nécessitent 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 tirs et de course, ainsi que pour les traceurs, l’audio réseau et la conversation vocale. La classe DatagramSocket fournit un socket UDP qu’il est possible d’utiliser dans les jeux de 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 les informations de l’utilisateur et les scores de jeu. Lorsqu’il est utilisé avec SSL pour la sécurité, un client HTTP peut être utilisé pour la connexion, l’achat, l’échange d’actifs, la création et la gestion des personnages de jeu. La classe HttpClient fournit une API de client HTTP moderne à utiliser dans les jeux de 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 important ou un échec. 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 qui entraînent des exceptions lors de l’utilisation d’API réseau :

  • Les données d’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’un URI.
  • Perte ou modification de la connectivité réseau.
  • Échecs de connexion réseau lors de l’utilisation des sockets ou des API clientES HTTP.
  • Erreurs de serveur réseau ou de point de terminaison distant.
  • Diverses erreurs de mise en réseau.

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 entraîner l’interruption de l’ensemble de votre application par le runtime.

Vous devez écrire du code capable de gérer les exceptions au moment où 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 en utilisant des 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 d’inclusion Winerror.h contient une grande liste de valeurs HRESULT possibles qui comprend des erreurs réseau.

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

  • Une méthode permettant de récupérer la valeur HRESULT de l’erreur qui a provoqué l’exception. La liste possible de valeurs potentielles de HRESULT 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.
  • Un 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 pour la classe HostName utilisée avec des sockets peut lever une exception si la chaîne transmise n’est pas un nom d’hôte valide (c’est-à-dire, si elle contient des caractères non autorisés dans un nom d’hôte). Si une application obtient une entrée de l’utilisateur pour le HostName d’une connexion homologue pour le jeu, le constructeur doit se trouver dans un bloc try/catch. Si une exception est levée, l’application peut notifier 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. Ceci peut s’avérer utile pour gérer différemment certaines exceptions réseau dans votre application.

Une erreur rencontrée sur l’opération 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 sert à convertir une erreur réseau résultant d’une opération de socket en valeur d’énumération SocketErrorStatus. La plupart des valeurs d’énumération SocketErrorStatus correspondent à une erreur renvoyée par l’opération native des sockets Windows. Votre application peut filtrer les valeurs d’énumération SocketErrorStatus pour modifier son comportement 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 obtenir des informations plus détaillées 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, la valeur HRESULT renvoyée est E\_INVALIDARG.

Ajouter du code pour gérer les exceptions lors de la tentative d’établir une connexion de socket 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::Http::HttpClient peut lever une exception si la chaîne transmise n’est pas un URI valide (si elle contient des caractères non autorisés dans un URI). En C++, aucune méthode ne permet d’essayer et d’analyser une chaîne passée à un URI. Si une application obtient une entrée de l’utilisateur pour la classe Windows::Foundation::Uri, le constructeur doit se trouver dans un bloc try/catch. Si une exception est levée, l’application peut notifier l’utilisateur et demander un nouveau 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. De ce fait, une application utilisant HttpClient et les autres classes de cet espace de noms doit utiliser la valeur HRESULT.

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

Pour la plupart des erreurs de validation de paramètre, la valeur HRESULT renvoyée est E\_INVALIDARG. Pour certains appels de méthodes non conformes, la valeur HRESULT retournée est E_ILLEGAL_METHOD_CALL.

Ajouter du code pour gérer les exceptions lors de l’utilisation de HttpClient pour la connexion à 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

Informations de référence

Exemples d’applications