Sdílet prostřednictvím


Sítě pro hry

Naučte se vyvíjet a začlenit síťové funkce do hry DirectX.

Přehled konceptů

Ve hře DirectX se dá používat celá řada síťových funkcí, ať už jde o jednoduchou samostatnou hru pro masivně více hráčů. Nejjednodušším využitím sítí by bylo ukládat uživatelská jména a skóre her na centrálním síťovém serveru.

Síťové rozhraní API jsou potřebná ve hrách pro více hráčů, které používají model infrastruktury (klient-server nebo internetový peer-to-peer), a také pro ad hoc hry (místní peer-to-peer hry). Pro serverové hry s více přehrávači se centrální herní server obvykle stará o většinu herních operací a klientská herní aplikace se používá pro vstup, zobrazení grafiky, přehrávání zvuku a dalších funkcí. Rychlost a latence síťových přenosů je problém s uspokojivým herním prostředím.

U her typu peer-to-peer každá hráčova aplikace zpracovává vstup a grafiku. Ve většině případů se hráči her nacházejí v těsné blízkosti, takže latence sítě by měla být nižší, ale stále se jedná o problém. Jak zjistit uzly sítě a vytvořit připojení se stává otázkou.

Pro hry s jedním hráčem se centrální webový server nebo služba často používají k ukládání uživatelských jmen, skóre her a dalších různých informací. V těchto hrách je rychlost a latence síťových přenosů méně starostí, protože nemá přímý vliv na provoz her.

Síťové podmínky se můžou kdykoli změnit, takže každá hra, která používá síťová rozhraní API, musí zpracovávat výjimky sítě, ke kterým může dojít. Další informace o zpracování výjimek sítě najdete v tématu Základy sítí.

Brány firewall a webové proxy servery jsou běžné a mohou ovlivnit možnost využívat síťové funkce. Hra, která využívá síťovou komunikaci, potřebuje být připravena na správné zpracování bran firewall a proxy serverů.

U mobilních zařízení je důležité monitorovat dostupné síťové prostředky a chovat se odpovídajícím způsobem v sítích účtovaných podle měrných dat, kde můžou být významné náklady na roaming nebo data.

Izolace sítě je součástí modelu zabezpečení aplikací používaného systémem Windows. Systém Windows aktivně zjišťuje hranice sítě a vynucuje omezení přístupu k síti pro izolaci sítě. Aplikace musí deklarovat možnosti izolace sítě, aby bylo možné definovat rozsah síťového přístupu. Bez deklarování těchto možností nebude mít vaše aplikace přístup k síťovým prostředkům. Další informace o tom, jak Systém Windows vynucuje izolaci sítě pro aplikace, najdete v tématu Postup konfigurace možností izolace sítě.

Aspekty návrhu

V hrách DirectX lze používat různá síťová rozhraní API. Proto je důležité vybrat správné rozhraní API. Windows podporuje řadu síťových rozhraní API, která vaše aplikace může použít ke komunikaci s jinými počítači a zařízeními prostřednictvím internetu nebo privátních sítí. Prvním krokem je zjistit, jaké síťové funkce vaše aplikace potřebuje.

Toto jsou nejoblíbenější síťová rozhraní API pro hry.

  • TCP a sokety – poskytuje spolehlivé připojení. Použijte protokol TCP pro herní operace, které nepotřebují zabezpečení. PROTOKOL TCP umožňuje snadno škálovat server, takže se běžně používá ve hrách, které používají model infrastruktury (klient-server nebo internetový peer-to-peer). TCP lze také používat pro ad hoc (místní peer-to-peer) hry přes Wi-Fi direct a Bluetooth. PROTOKOL TCP se běžně používá pro pohyb herních objektů, interakci znaků, textový chat a další operace. Třída StreamSocket poskytuje soket TCP, který lze použít ve hrách v Microsoft Storu. Třída StreamSocket se používá se souvisejícími třídami v Windows::Networking::Sockets jmenném prostoru.
  • Protokoly TCP a sokety používající protokol SSL – poskytuje spolehlivé připojení, které zabraňuje odposlouchávání. Pro herní operace, které potřebují zabezpečení, používejte připojení TCP s protokolem SSL. Šifrování a režijní náklady na SSL zvyšuje latenci a výkon, takže se používá jenom v případě, že je potřeba zabezpečení. PROTOKOL TCP s PROTOKOLem SSL se běžně používá pro přihlášení, nákup a obchodování aktiv, vytváření herních znaků a správu. StreamSocket třída poskytuje soket TCP, který podporuje SSL.
  • UDP a sokety – poskytuje nespolehlivé síťové přenosy s nízkou režií. UDP se používá pro herní operace, které vyžadují nízkou latenci a mohou tolerovat ztrátu paketů. Často se používá pro bojové hry, střelecké hry, trasery, síťový zvuk a hlasový chat. Třída DatagramSocket poskytuje soket UDP, který lze použít ve hrách Microsoft Store. Třída DatagramSocket se používá se souvisejícími třídami v oboru názvů Windows::Networking::Sockets.
  • Klient HTTP – poskytuje spolehlivé připojení k serverům HTTP. Nejběžnějším scénářem sítě je přístup k webu za účelem načtení nebo uložení informací. Jednoduchým příkladem by byla hra, která používá web k ukládání informací o uživateli a skóre her. Při použití s PROTOKOLem SSL pro zabezpečení lze použít klienta HTTP pro přihlášení, nákup, obchodování aktiv, vytváření herních znaků a správu. Třída HttpClient poskytuje moderní rozhraní API klienta HTTP pro použití ve hrách v Microsoft Storu. Třída HttpClient se používá se souvisejícími třídami v Windows::Web::Http oboru názvů.

Zpracování výjimek sítě ve hře DirectX

Pokud ve hře DirectX dojde k výjimce sítě, značí to významný problém nebo selhání. K výjimkám může dojít z mnoha důvodů při používání síťových rozhraní API. Výjimka může často vést ke změnám připojení k síti nebo jiným problémům se sítí se vzdáleným hostitelem nebo serverem.

Mezi příčiny výjimek při používání síťových rozhraní API patří:

  • Vstup od uživatele pro název hostitele nebo identifikátor URI obsahuje chyby a není platný.
  • Problémy s vyřešením názvů při vyhledávání názvu hostitele nebo URI.
  • Ztráta nebo změna připojení k síti
  • Selhání síťového připojení pomocí soketů nebo rozhraní API klienta HTTP
  • Chyby síťového serveru nebo vzdáleného koncového bodu
  • Různé síťové chyby.

K výjimkám ze síťových chyb (například ztráta nebo změna připojení, selhání připojení a selhání serveru) může dojít kdykoli. Tyto chyby vedou k vyvolání výjimek. Pokud vaše aplikace nezpracuje výjimku, může to způsobit ukončení celé aplikace modulem runtime.

Při volání většiny asynchronních síťových metod je nutné napsat kód pro zpracování výjimek. V některých případech, když dojde k výjimce, může být síťová metoda opakována jako způsob, jak problém vyřešit. Jindy může být potřeba, aby vaše aplikace pokračovala bez připojení k síti pomocí dříve uložených dat v mezipaměti.

Aplikace univerzální platformy Windows (UPW) obvykle můžou vyvolat jedinou výjimku. Obslužná rutina výjimky může načíst podrobnější informace o příčině výjimky, aby lépe porozumělo selhání a učinilo příslušná rozhodnutí.

Pokud dojde k výjimce ve hře DirectX, která je aplikací pro UPW, je možné načíst hodnotu HRESULT pro příčinu chyby. Soubor záhlaví Winerror.h obsahuje velký seznam možných hodnot HRESULT, které zahrnují chyby sítě.

Síťová rozhraní API podporují různé metody načítání těchto podrobných informací o příčině výjimky.

  • Metoda pro získání hodnoty chyby HRESULT , která způsobila výjimku. Možný seznam potenciálních hodnot HRESULT je velký a nespecifikovaný. Hodnotu HRESULT je možné načíst při použití libovolného síťového rozhraní API.
  • Pomocná metoda, která převede hodnotu HRESULT na hodnotu výčtu. Seznam možných hodnot výčtu je určen a relativně malý. Pomocná metoda je k dispozici pro třídy soketů ve Windows::Networking::Sockets.

Výjimky ve Windows.Networking.Sockets

Konstruktor pro HostName třídu, která se používá se sokety, může vyvolat výjimku, pokud je předaný řetězec neplatným názvem hostitele (obsahuje nepovolené znaky). Pokud aplikace dostává vstup od uživatele pro HostName pro herní partnerské připojení, konstruktor by měl být v bloku try/catch. Pokud dojde k výjimce, aplikace může uživatele upozornit a požádat o nový název hostitele.

Přidat kód pro ověření řetězce uživatelského názvu hostitele

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

Windows.Networking.Sockets jmenný prostor má praktické pomocné funkce a výčty pro zpracování chyb při použití soketů. To může být užitečné pro zpracování konkrétních výjimek sítě v aplikaci jinak.

Při operaci na DatagramSocket, StreamSocketnebo StreamSocketListener dojde k vyvolání výjimky. Příčinou výjimky je chybová hodnota reprezentovaná jako hodnota HRESULT . Metoda SocketError.GetStatus slouží k převodu síťové chyby z operace soketu na hodnotu výčtu SocketErrorStatus . Většina hodnot výčtu SocketErrorStatus odpovídá chybě vrácené nativní operací soketů systému Windows. Aplikace může filtrovat konkrétní SocketErrorStatus hodnoty výčtu, které upraví chování aplikace v závislosti na příčině výjimky.

V případě chyb ověření parametrů může aplikace také použít HRESULT z výjimky, aby se dozvěděla podrobnější informace o chybě, která výjimku způsobila. Možné hodnoty HRESULT jsou uvedeny v hlavičkovém souboru Winerror.h. U většiny chyb ověření parametru je vráceno HRESULT jako E_INVALIDARG.

Přidejte kód pro zpracování výjimek při pokusu o připojení pomocí streamového soketu.

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

Výjimky ve Windows.Web.Http

Konstruktor třídy Windows::Foundation::Uri používané s Windows::Web::Http::HttpClient může vyvolat výjimku, pokud předaný řetězec není platný URI (obsahuje znaky, které nejsou povoleny v URI). V jazyce C++ neexistuje žádná metoda, která by se pokusila analyzovat řetězec na identifikátor URI. Pokud aplikace získá vstup od uživatele pro Windows::Foundation::Uri, konstruktor by měl být v bloku try/catch. Pokud dojde k vyvolání výjimky, aplikace může uživatele upozornit a požádat o nový identifikátor URI.

Vaše aplikace by také měla zkontrolovat, že schéma v identifikátoru URI je HTTP nebo HTTPS, protože toto jsou jediná schémata podporovaná systémem Windows::Web::Http::HttpClient.

Přidejte kód pro ověření řetězce URI uživatelem

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

Windows::Web::Http jmenný prostor postrádá praktickou funkci. Aplikace využívající HttpClient a další třídy v tomto oboru názvů proto musí používat hodnotu HRESULT .

V aplikacích používajících jazyk C++ představuje platforma::Exception chybu při provádění aplikace, když dojde k výjimce. Vlastnost Platform::Exception::HResult vrátí HRESULT přiřazené ke konkrétní výjimce. Vlastnost Platform::Exception::Message vrátí řetězec přidružený k hodnotě HRESULT, který je poskytnut systémem. Možné hodnoty HRESULT jsou uvedeny v hlavičkovém souboru Winerror.h. Aplikace může filtrovat konkrétní hodnoty HRESULT a měnit chování aplikace v závislosti na příčině výjimky.

U většiny chyb ověření parametru je vráceno HRESULT jako E_INVALIDARG. U některých neplatných volání metody je vrácen HRESULT jako E_ILLEGAL_METHOD_CALL.

Přidání kódu pro zpracování výjimek při pokusu o připojení k serveru HTTP pomocí HttpClient

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

Další zdroje informací

Odkazy

Ukázkové aplikace