Netzwerk für Spiele

Hier erfahren Sie, wie Sie Netzwerkfeatures entwickeln und in ein DirectX-Spiel integrieren.

Konzepte auf einen Blick

In Ihrem DirectX-Spiel kann eine Vielzahl von Netzwerkfeatures verwendet werden, unabhängig davon, ob es sich um ein einfaches eigenständiges Spiel oder massive Multi-Player-Spiele handelt. Die einfachste Verwendung des Netzwerks wäre das Speichern von Benutzernamen und Spielergebnissen auf einem zentralen Netzwerkserver.

Netzwerk-APIs werden in Spielen mit mehreren Spielern benötigt, die das Infrastrukturmodell (Clientserver oder Internetpeer-to-Peer) und auch Ad-hoc-Spiele (lokale Peer-to-Peer-Spiele) verwenden. Bei serverbasierten Multi-Player-Spielen verarbeitet ein zentraler Spielserver in der Regel die meisten Spielvorgänge und die Clientspiel-App wird für Eingaben, das Anzeigen von Grafiken, die Wiedergabe von Audio und für andere Features verwendet. Geschwindigkeit und Latenz von Netzwerkübertragungen können ein Problem für eine zufriedenstellende Spielerfahrung darstellen.

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 problematisch sein kann. Wie Peers ermittelt werden und eine Verbindung hergestellt wird, ist ein Problem.

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 sind die Geschwindigkeit und Latenz von Netzwerkübertragungen weniger beunruhigend, da sie sich nicht direkt auf den Spielbetrieb auswirken.

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

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

Für mobile Geräte ist es wichtig, die verfügbaren Netzwerkressourcen zu überwachen und sich entsprechend zu verhalten, wenn sie sich in gebührenpflichtigen Netzwerken befinden, in denen Roaming- oder Datenkosten erheblich sein 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. Wenn diese Funktion nicht deklariert ist, hat Ihre App keinen Zugriff auf Netzwerkressourcen. Weitere Informationen dazu, wie Windows die Netzwerkisolation für Apps erzwingt, finden Sie unter Konfigurieren von Netzwerkisolationsfunktionen.

Überlegungen zum Entwurf

In DirectX-Spielen kann eine Vielzahl von Netzwerk-APIs 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 ein einfaches Skalieren, sodass es 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 WLAN 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 Windows 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 Overhead von SSL verursachen zusätzliche Kosten in Bezug auf Latenz und Leistung, so dass es nur verwendet wird, wenn Sicherheit erforderlich ist. TCP mit SSL wird üblicherweise für die Anmeldung, den Kauf und den Handel mit Assets und die Erstellung und Verwaltung von Spielcharakteren 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. Es wird häufig für Kampfspiele, Shooter und Tracer, Netzwerk-Audio und Voice-Chat verwendet. Die DatagramSocket-Klasse stellt einen UDP-Socket bereit, der in Windows Store-Spielen verwendet werden kann. Die DatagramSocket-Klasse wird 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 von SSL für die Sicherheit kann ein HTTP-Client für die Anmeldung, den Kauf, den Handel mit Assets, die Erstellung von Spielcharakteren und die Verwaltung verwendet werden. Die HttpClient-Klasse bietet eine moderne HTTP-Client-API zur Verwendung in Windows Store-Spielen. Die HttpClient-Klasse wird mit verwandten Klassen im Windows::Web::Http-Namespace verwendet.

Behandeln von Netzwerkausnahmen in Ihrem DirectX-Spiel

Wenn eine Netzwerkausnahme 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 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 mithilfe von Sockets oder HTTP-Client-APIs.
  • Netzwerkserver- oder Remote-Endpunktfehler.
  • 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 von Ihrer App nicht behandelt wird, kann dies dazu führen, dass die gesamte App von der Laufzeit beendet wird.

Beim Aufrufen der meisten asynchronen Netzwerkmethoden müssen Sie Code zum Behandeln von Ausnahmen schreiben. Wenn eine Ausnahme auftritt, kann manchmal eine Netzwerkmethode erneut versucht werden, um das Problem zu lösen. In anderen Fällen muss Ihre App möglicherweise planen, ohne Netzwerkverbindung mit zuvor zwischengespeicherten Daten fortzufahren.

Apps für die universelle Windows-Plattform (UWP) lösen in der Regel eine einzige 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-Includedatei enthält eine umfangreiche Liste möglicher HRESULT-Werte, die Netzwerkfehler beinhaltet.

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

  • Eine Methode zum Abrufen des HRESULT-Werts des Fehlers, der die Ausnahme verursacht hat. Die mögliche Liste potenzieller HRESULT-Werte ist groß und unspezifisch. Der HRESULT-Wert kann bei Verwendung einer der Netzwerk-APIs abgerufen werden.
  • Eine Hilfsmethode, die den HRESULT-Wert in einen Enumerationswert konvertiert. Die Liste der möglichen Enumerationswerte ist spezifisch und relativ klein. Eine Hilfsmethode ist für die Socketklassen in Windows ::Networking::Sockets verfü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 (Zeichen enthält, die in einem Hostnamen nicht zulässig sind). Wenn eine App vom Benutzer den HostName für eine Peer-Verbindung zum Spielen abfragt, sollte der Konstruktor in einem Try/Catch-Block stehen. 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. Mit ihnen lassen sich spezifische Netzwerkausnahmen in der App unterschiedlich behandeln.

Ein Fehler in einem DatagramSocket-, StreamSocket- oder StreamSocketListener-Vorgang führt zum Auslösen einer Ausnahme. 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 vom nativen Windows Sockets-Vorgang zurückgegebenen Fehler. Eine App kann bestimmte SocketErrorStatus-Enumerationswerte filtern, um das App-Verhalten je nach Ausnahmeursache zu ändern.

Bei Parameterüberprüfungsfehlern kann eine App auch das HRESULT aus der Ausnahme verwenden, um ausführlichere Informationen zum Fehler zu erhalten, der die Ausnahme verursacht hat. Mögliche HRESULT-Werte sind in der Headerdatei Winerror.h aufgeführt. Für die meisten Parameterüberprüfungsfehler wird der HRESULT-Wert E_INVALIDARG zurückgegeben.

Hinzufügen von Code zum Behandeln von Ausnahmen beim Versuch, eine Datenstromsocketverbindung 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 mit Windows::Web::Http::HttpClient kann eine Ausnahme auslösen, wenn die übergebene Zeichenfolge kein gültiger URI ist (Zeichen enthält, die in einem URI nicht zulässig sind). In C++ gibt es keine Methode zum Analysieren einer Zeichenfolge für einen URI. Wenn eine App vom Benutzer eine Eingabe für das Windows::Foundation::Uri-Element erhält, sollte sich der Konstruktor innerhalb eines try/catch-Blocks befinden. Wenn eine Ausnahme ausgelöst wird, kann die App den Benutzer benachrichtigen und einen neuen URI anfordern.

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

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

    // 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 Komfortfunktion. Eine App, die HttpClient und andere Klassen in diesem Namespace verwendet, muss daher den HRESULT-Wert verwenden.

In Apps, die C++ verwenden, stellt das Platform::Exception-Objekt einen Fehler während der App-Ausführung dar, wenn eine Ausnahme auftritt. Die Platform::Exception::HResult-Eigenschaft gibt den HRESULT-Wert zurück, der der jeweiligen Ausnahme zugewiesen ist. 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 Headerdatei Winerror.h aufgeführt. Eine App kann nach bestimmten HRESULT-Werten filtern, um das App-Verhalten je nach Ausnahmeursache zu ändern.

Für die meisten Parameterüberprüfungsfehler wird der HRESULT-Wert E_INVALIDARG zurückgegeben. Bei manchen unzulässigen Methodenaufrufen wird der HRESULT-Wert E_ILLEGAL_METHOD_CALL zurückgegeben.

Hinzufügen von Code zum Behandeln von Ausnahmen beim Versuch, HttpClient zum Herstellen einer Verbindung mit einem HTTP-Server zu verwenden

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

Verweis

Beispiel-Apps