Freigeben über


Netzwerk für Spiele

Erfahren Sie, wie Sie Netzwerkfeatures in Ihr DirectX-Spiel entwickeln und integrieren.

Konzepte auf einen Blick

Eine Vielzahl von Netzwerkfeatures kann in Ihrem DirectX-Spiel verwendet werden, sei es ein einfaches eigenständiges Spiel oder ein massiv Mehrspieler-Spiel. Die einfachste Verwendung des Netzwerks wäre das Speichern von Benutzernamen und Spielergebnissen auf einem zentralen Netzwerkserver.

Netzwerk-APIs werden in Mehrspieler-Spielen benötigt, die das Infrastrukturmodell (Client-Server oder Internet-Peer-to-Peer) verwenden, und auch von Ad-hoc-Spielen (lokale Peer-to-Peer-Spiele). Bei serverbasierten Multi-Player-Spielen verarbeitet ein zentraler Spielserver in der Regel die meisten Spielvorgänge und die Clientspiel-App wird für Eingaben, Anzeigen von Grafiken, Wiedergeben von Audio und anderen Features verwendet. Die Geschwindigkeit und Latenz von Netzwerkübertragungen ist ein Problem für eine zufriedenstellende Spielerfahrung.

Bei Peer-to-Peer-Spielen verarbeitet die App jedes Spielers die Eingaben und Grafiken. In den meisten Fällen befinden sich die Spieler in unmittelbarer Nähe, sodass die Netzwerklatenz niedriger sein sollte, aber dennoch ein Problem ist. Wie die Ermittlung von Peers und die Herstellung einer Verbindung zu einem Problem wird.

Bei Spielen mit einem Spieler wird häufig ein zentraler Webserver oder Dienst verwendet, um Benutzernamen, Spielstände und andere sonstige Informationen zu speichern. Bei diesen Spielen ist die Geschwindigkeit und Latenz von Netzwerkübertragungen weniger beunruhigend, da sie sich nicht direkt auf den Spielbetrieb auswirkt.

Netzwerkbedingungen können sich jederzeit ändern, daher muss jedes Spiel, das Netzwerk-APIs verwendet, Netzwerk-Ausnahmen verarbeiten können, die auftreten können. Weitere Informationen zum Behandeln von Netzwerkausnahmen finden Sie unter Netzwerkgrundlagen.

Firewalls und Webproxys sind häufig und können sich auf die Verwendung von Netzwerkfeatures auswirken. Ein Spiel, das Netzwerk verwendet, muss darauf vorbereitet sein, Firewalls und Proxys ordnungsgemäß zu verarbeiten.

Für mobile Geräte ist es von Bedeutung, die verfügbaren Netzwerkressourcen zu überwachen und sich entsprechend einzurichten, wenn es sich um getaktete Netzwerke handelt, bei denen Roaming- oder Datenkosten anfallen können.

Die Netzwerkisolation ist Teil des von Windows verwendeten App-Sicherheitsmodells. Windows erkennt aktiv Netzwerkgrenzen und erzwingt Netzwerkzugriffseinschränkungen für die Netzwerkisolation. Apps müssen Netzwerkisolationsfunktionen deklarieren, um den Umfang des Netzwerkzugriffs zu definieren. Ohne diese Funktionen zu deklarieren, hat Ihre App keinen Zugriff auf Netzwerkressourcen. Weitere Informationen dazu, wie Windows die Netzwerkisolation für Apps erzwingt, finden Sie unter Konfigurieren von Netzwerkisolationsfunktionen.

Entwurfsüberlegungen

Eine Vielzahl von Netzwerk-APIs kann in DirectX-Spielen verwendet werden. Daher ist es wichtig, die richtige API zu wählen. Windows unterstützt eine Vielzahl von Netzwerk-APIs, die Ihre App für die Kommunikation mit anderen Computern und Geräten über das Internet oder private Netzwerke verwenden kann. Der erste Schritt besteht darin, herauszufinden, welche Netzwerkfeatures Ihre App benötigt.

Dies sind die beliebtesten Netzwerk-APIs für Spiele.

  • TCP und Sockets – Bietet eine zuverlässige Verbindung. Verwenden Sie TCP für Spielvorgänge, die keine Sicherheit benötigen. TCP ermöglicht es dem Server, einfach zu skalieren, sodass er häufig in Spielen verwendet wird, die das Infrastrukturmodell (Clientserver oder Internetpeer-to-Peer) verwenden. TCP kann auch von Ad-hoc-Spielen (lokale Peer-to-Peer-Spiele) über Wi-Fi Direct und Bluetooth verwendet werden. TCP wird häufig für die Bewegung von Spielobjekten, die Zeicheninteraktion, den Textchat und andere Vorgänge verwendet. Die StreamSocket-Klasse stellt einen TCP-Socket bereit, der in Microsoft Store-Spielen verwendet werden kann. Die StreamSocket- Klasse wird mit verwandten Klassen im Windows::Networking::Sockets Namespace verwendet.
  • TCP und Sockets mit SSL – Bietet eine zuverlässige Verbindung, die Lauschangriffe verhindert. Verwenden Sie TCP-Verbindungen mit SSL für Spielvorgänge, die Sicherheit benötigen. Die Verschlüsselung und der Aufwand von SSL erzeugt Kosten in Latenz und Performance, daher wird sie nur verwendet, wenn Sicherheit erforderlich ist. TCP mit SSL wird häufig für Anmeldungen, Einkäufe, den Handel mit Vermögenswerten sowie die Erstellung und Verwaltung von Spielfiguren verwendet. Die StreamSocket-Klasse stellt einen TCP-Socket bereit, der SSL unterstützt.
  • UDP und Sockets – Bietet unzuverlässige Netzwerkübertragungen mit geringem Mehraufwand. UDP wird für Spielvorgänge verwendet, die eine geringe Latenz erfordern und einige Paketverluste tolerieren können. Dies wird häufig für Kampfspiele, Schießspiele und Spureneffekte, Audioübertragung im Netzwerk und Sprachchat verwendet. Die DatagramSocket-Klasse stellt einen UDP-Socket bereit, der in Microsoft Store-Spielen verwendet werden kann. Die DatagramSocket--Klasse wird zusammen mit verwandten Klassen im Windows::Networking::Sockets-Namespace verwendet.
  • HTTP-Client – Stellt eine zuverlässige Verbindung zu HTTP-Servern bereit. Das häufigste Netzwerkszenario besteht darin, auf eine Website zuzugreifen, um Informationen abzurufen oder zu speichern. Ein einfaches Beispiel wäre ein Spiel, das eine Website zum Speichern von Benutzerinformationen und Spielergebnissen verwendet. Bei Verwendung mit SSL zur Sicherheit kann ein HTTP-Client für die Anmeldung, den Kauf, den Handel von Objekten, die Erstellung von Spielfiguren und die Verwaltung verwendet werden. Die HttpClient-Klasse stellt eine moderne HTTP-Client-API für die Verwendung in Microsoft Store-Spielen bereit. Die HttpClient-Klasse wird mit verwandten Klassen im Windows::Web::Http-Namespace verwendet.

Umgang mit Netzwerkausnahmen in Ihrem DirectX-Spiel

Wenn eine Netzwerk ausnahme in Ihrem DirectX-Spiel auftritt, weist dies auf ein erhebliches Problem oder einen Fehler hin. Ausnahmen können aus vielen Gründen auftreten, wenn Netzwerk-APIs verwendet werden. Häufig kann die Ausnahme zu Änderungen in der Netzwerkkonnektivität oder anderen Netzwerkproblemen mit dem Remotehost oder Server führen.

Einige Ursachen für Ausnahmen bei der Verwendung von Netzwerk-APIs sind die folgenden:

  • Die Eingabe des Benutzers für einen Hostnamen oder einen URI enthält Fehler und ist ungültig.
  • Fehler bei der Namensauflösung beim Nachschlagen eines Hostnamens oder eines URi.
  • Verlust oder Änderung der Netzwerkkonnektivität.
  • Netzwerkverbindungsfehler bei der Verwendung von Sockets oder HTTP-Client-APIs.
  • Netzwerkserver- oder Remoteendpunktfehler.
  • Verschiedene Netzwerkfehler.

Ausnahmen von Netzwerkfehlern (z. B. Verlust oder Änderung der Konnektivität, Verbindungsfehler und Serverfehler) können jederzeit auftreten. Diese Fehler führen dazu, dass Ausnahmen ausgelöst werden. Wenn eine Ausnahme nicht von Ihrer App behandelt wird, kann dies dazu führen, dass Ihre gesamte App durch die Laufzeitumgebung beendet wird.

Sie müssen Code schreiben, um Ausnahmen zu behandeln, wenn Sie die meisten asynchronen Netzwerkmethoden aufrufen. Manchmal kann eine Netzwerkmethode, wenn eine Ausnahme auftritt, erneut versucht werden, um das Problem zu beheben. In anderen Zeiten muss Ihre App möglicherweise planen, ohne netzwerkkonnektivität mit zuvor zwischengespeicherten Daten fortzufahren.

Apps für die universelle Windows-Plattform (UWP) lösen in der Regel eine einzelne Ausnahme aus. Ihr Ausnahmehandler kann detailliertere Informationen zur Ursache der Ausnahme abrufen, um den Fehler besser zu verstehen und geeignete Entscheidungen zu treffen.

Wenn eine Ausnahme in einem DirectX-Spiel auftritt, bei dem es sich um eine UWP-App handelt, kann der HRESULT-Wert für die Ursache des Fehlers abgerufen werden. Die Winerror.h--Include-Datei enthält eine große Liste möglicher HRESULT--Werte, einschließlich Netzwerkfehlern.

Die Netzwerk-APIs unterstützen verschiedene Methoden zum Abrufen dieser detaillierten Informationen zur Ursache einer Ausnahme.

  • Eine Methode, um den HRESULT- Wert des Fehlers abzurufen, der die Ausnahme verursacht hat. Die potenzielle Liste möglicher HRESULT- Werte ist groß und nicht definiert. Der HRESULT--Wert kann bei Verwendung einer der Netzwerk-APIs abgerufen werden.
  • Eine Hilfsmethode, die den HRESULT- Wert in einen Enumerationswert umwandelt. Die Liste der möglichen Enumerationswerte ist angegeben und relativ klein. Eine Hilfsmethode ist für die Socketklassen in der Windows::Networking::Socketsverfügbar.

Ausnahmen in Windows.Networking.Sockets

Der Konstruktor für die mit Sockets verwendete HostName-Klasse kann eine Ausnahme auslösen, wenn die übergebene Zeichenfolge kein gültiger Hostname ist (enthält Zeichen, die in einem Hostnamen nicht zulässig sind). Wenn eine App Eingaben vom Benutzer für die HostName- für eine Peerverbindung für Spiele erhält, sollte sich der Konstruktor in einem Try/Catch-Block befinden. Wenn eine Ausnahme ausgelöst wird, kann die App den Benutzer benachrichtigen und einen neuen Hostnamen anfordern.

Hinzufügen von Code zum Überprüfen einer Zeichenfolge für einen Hostnamen vom Benutzer

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

Der Windows.Networking.Sockets Namespace verfügt über praktische Hilfsmethoden und Enumerationen zum Behandeln von Fehlern bei der Verwendung von Sockets. Dies kann nützlich sein, um bestimmte Netzwerkausnahmen in Ihrer App anders zu behandeln.

Fehler bei DatagramSocket-, StreamSocket-oder StreamSocketListener Vorgang löst eine Ausnahme aus. Die Ursache der Ausnahme ist ein Fehlerwert, der als HRESULT-Wert dargestellt wird. Die SocketError.GetStatus--Methode wird verwendet, um einen Netzwerkfehler aus einem Socketvorgang in einen SocketErrorStatus Enumerationswert zu konvertieren. Die meisten SocketErrorStatus- Enumerationswerte entsprechen einem Fehler, der vom systemeigenen Windows-Sockets-Vorgang zurückgegeben wird. Eine App kann nach bestimmten SocketErrorStatus- Enumerationswerten filtern, um das App-Verhalten abhängig von der Ursache der Ausnahme zu ändern.

Bei Parameterüberprüfungsfehlern kann eine App auch die HRESULT aus der Ausnahme verwenden, um ausführlichere Informationen über den Fehler zu erhalten, der die Ausnahme verursacht hat. Mögliche HRESULT--Werte sind in der Winerror.h-Header-Datei aufgeführt. Bei den meisten Parameterüberprüfungsfehlern wird das HRESULT zurückgegeben, wobei E_INVALIDARGder ist.

Code hinzufügen, um Ausnahmen zu behandeln, wenn versucht wird, eine Stream-Socket-Verbindung herzustellen.

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

Ausnahmen in Windows.Web.Http

Der Konstruktor für die Windows::Foundation::Uri Klasse, die mit Windows::Web::Http::HttpClient verwendet wird, kann eine Exception werfen, wenn die übergebene Zeichenfolge kein gültiger URI ist (das heißt, sie enthält Zeichen, die in einem URI nicht zulässig sind). In C++ gibt es keine Methode, um zu versuchen, eine Zeichenfolge zu einem URI zu parsen. Wenn eine App Eingaben vom Benutzer für die Windows::Foundation::Urierhält, sollte sich der Konstruktor in einem try/catch-Block befinden. Wenn eine Ausnahme ausgelöst wird, kann die App den Benutzer benachrichtigen und einen neuen URI anfragen.

Ihre App sollte auch überprüfen, ob das Schema im URI HTTP oder HTTPS ist, da dies die einzigen vom Windows::Web::Http::HttpClientunterstützten Schemen sind.

Code hinzufügen, um eine Zeichenfolge für eine vom Benutzer eingegebene URI zu validieren.

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

Der Windows::Web::Http Namespace verfügt nicht über eine praktische Funktion. Daher muss eine App, die HttpClient und andere Klassen in diesem Namespace verwendet, den HRESULT-Wert verwenden.

In Apps mit C++ stellt die Platform::Exception einen Fehler während der App-Ausführung dar, wenn eine Ausnahme auftritt. Die Platform::Exception::HResult-Eigenschaft gibt die HRESULT- zurück, die der jeweiligen Ausnahme zugewiesen wurde. Die Platform::Exception::Message-Eigenschaft gibt die vom System bereitgestellte Zeichenfolge zurück, die dem HRESULT--Wert zugeordnet ist. Mögliche HRESULT--Werte sind in der Winerror.h-Header-Datei aufgeführt. Eine App kann je nach Ausnahmeursache nach bestimmten HRESULT- Werten filtern, um das App-Verhalten zu ändern.

Bei den meisten Parameterüberprüfungsfehlern wird das HRESULT zurückgegeben, wobei E_INVALIDARGder ist. Bei einigen unzulässigen Methodenaufrufen wird das zurückgegebene HRESULT als E_ILLEGAL_METHOD_CALLzurückgegeben.

Fügen Sie Code hinzu, um Ausnahmen zu behandeln, wenn versucht wird, HttpClient- zu verwenden, um eine Verbindung zu einem HTTP-Server herzustellen.

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

Weitere Ressourcen

Referenz

Beispiel-Apps