遊戲的網路功能

瞭解如何在 DirectX 遊戲中開發和納入網路功能。

概念簡介

DirectX 遊戲中可以使用各種網路功能,無論是簡單的獨立遊戲還是大型多玩家遊戲。 網路功能最簡單的用法是將使用者名稱和遊戲分數儲存在中央網路伺服器上。

使用基礎結構 (用戶端伺服器或網際網路對等) 模型,以及特定 (本機點對點) 遊戲的多玩家遊戲也需要網路 API。 對於伺服器型多玩家遊戲,中央遊戲伺服器通常會處理大部分的遊戲作業,而用戶端遊戲應用程式則用於輸入、顯示圖形、播放音訊及其他功能。 網路傳輸的速度和延遲是遊戲體驗滿意度所關注的問題。

對於點對點遊戲,每個玩家的應用程式都會處理輸入和圖形。 在大部分情況下,遊戲玩家會位於鄰近位置,因此網路延遲應該較低,但仍然是個問題。 如何探索對等並建立連線會成為問題。

對於單一玩家遊戲,中央網頁伺服器或服務通常用來儲存使用者名稱、遊戲分數及其他資訊。 在這些遊戲中,網路傳輸的速度和延遲較不擔心,因為它不會影響遊戲作業。

網路狀況可以隨時變更,因此任何使用網路 API 的遊戲都必須處理可能發生的網路例外狀況。 若要深入瞭解處理網路例外狀況,請參閱網路基本概念

防火牆與 Web Proxy 很常見,可能會影響使用網路功能的能力。 使用網路的遊戲必須準備好妥善處理防火牆及 Proxy。

對於行動裝置,請務必監視可用的網路資源,並在漫遊或資料成本可能相當重要的計量網路上運作。

網路隔離是 Windows 所使用應用程式安全性模型的一部分。 Windows 會主動探索網路界限,並強制執行網路隔離的網路存取限制。 應用程式必須宣告網路隔離功能,才能定義網路存取的範圍。 若未宣告這些功能,您的應用程式將無法存取網路資源。 若要深入瞭解 Windows 如何針對應用程式強制執行網路隔離,請參閱如何設定網路隔離功能

設計考量

DirectX 遊戲可使用各種網路 API。 因此,請務必挑選正確的 API。 Windows 支援各種網路 API,您的應用程式可用來透過網際網路或私人網路與其他電腦和裝置通訊。 您的第一個步驟是瞭解應用程式所需的網路功能。

這些是較受歡迎的遊戲網路 API。

  • TCP 與通訊端 - 提供可靠的連線。 針對不需要安全性的遊戲作業使用 TCP。 TCP 可讓伺服器輕鬆地調整規模,因此通常會在使用基礎結構 (用戶端伺服器或網際網路對等) 模型的遊戲中使用。 TCP 也可用於透過 Wi-Fi Direct 和藍牙進行的臨機操作 (本機點對點) 遊戲。 TCP 通常用於遊戲物件移動、字元互動、文字聊天及其他作業。 StreamSocket 類別提供可在 Microsoft Store 遊戲中使用的 TCP 通訊端。 StreamSocket 類別會與 Windows::Networking::Sockets 命名空間中的相關類別搭配使用。
  • 使用 SSL 的 TCP 和通訊端 - 提供防止竊聽的可靠連線。 針對需要安全性的遊戲作業使用 TCP 連線與 SSL。 SSL 的加密與額外負荷會增加延遲和效能的成本,因此只有在需要安全性時才會使用。 搭配 SSL 的 TCP 通常用於登入、購買與交易資產、遊戲字元建立及管理。 StreamSocket 類別提供支援 SSL 的 TCP 通訊端。
  • UDP 和通訊端 - 以低負荷提供不可靠的網路傳輸。 UDP 用於需要低延遲且可以容許某些封包遺失的遊戲作業。 這通常用於戰鬥遊戲、射擊與追蹤、網路音訊及語音聊天。 DatagramSocket 類別提供可在 Microsoft Store 遊戲中使用的 UDP 通訊端。 DatagramSocket 類別會與 Windows::Networking::Sockets 命名空間中的相關類別搭配使用。
  • HTTP 用戶端 - 提供與 HTTP 伺服器的可靠連線。 最常見的網路案例是存取網站以擷取或儲存資訊。 簡單的範例是使用網站來儲存使用者資訊和遊戲分數的遊戲。 搭配 SSL 使用安全性時,HTTP 用戶端可用於登入、購買、交易資產、遊戲字元建立及管理。 HttpClient 類別提供新式 HTTP 用戶端 API,可用於 Microsoft Store 遊戲。 HttpClient 類別會與 Windows::Web::Http 命名空間中的相關類別搭配使用。

處理 DirectX 遊戲中的網路例外狀況

當 DirectX 遊戲中發生網路例外狀況時,這表示發生重大問題或失敗。 使用網路 API 時,可能會因為許多原因而發生例外狀況。 通常,例外狀況可能是由於遠端主機或伺服器的網路連線變更或其他網路問題造成的。

使用網路 API 時發生例外狀況的一些原因包括:

  • 使用者輸入的主機名稱或 URI 包含錯誤且無效。
  • 查閱主機名稱或 URI 時,名稱解析失敗。
  • 網路連線中斷或變更。
  • 使用通訊端或 HTTP 用戶端 API 的網路連線失敗。
  • 網路伺服器或遠端端點錯誤。
  • 其他網路錯誤。

網路錯誤 (例如連線中斷或變更、連線失敗及伺服器失敗) 的例外狀況隨時都可能發生。 這些錯誤會導致擲回例外狀況。 如果應用程式未處理例外狀況,可能會導致整個應用程式在執行階段終止。

呼叫大部分非同步網路方法時,您必須撰寫程式碼來處理例外狀況。 有時,當發生例外狀況時,可以重試網路方法作為解決問題的方式。 其他時候,您的應用程式可能需要規劃繼續使用先前快取的資料,而不需要網路連線。

通用 Windows 平台 (UWP) 應用程式通常會擲回單一例外狀況。 您的例外狀況處理常式可擷取例外狀況發生原因的詳細資訊,以進一步瞭解失敗並做出適當決策。

在 UWP 應用程式的 DirectX 遊戲中發生例外狀況時,可以擷取錯誤原因的 HRESULT 值。 Winerror.h 所包含的檔案含有大量可能的 HRESULT 值清單,包括網路錯誤。

網路 API 可透過不同方法,擷取例外狀況發生原因的詳細資訊。

  • 擷取造成例外狀況之錯誤 HRESULT 值的方法。 潛在 HRESULT 值的可能清單很大且未指定。 使用任何網路 API 時,可以擷取 HRESULT 值。
  • 協助程式方法可將 HRESULT 值轉換成列舉值。 可能列舉值的清單已指定且相對較少。 Windows::Networking::Sockets中的通訊端類別可以使用協助程式方法。

Windows.Networking.Sockets 中的例外狀況

搭配通訊端使用的 HostName 類別的建構函式,如果傳遞的字串不是有效的主機名稱,則可能會擲回例外狀況 (包含主機名稱中不允許的字元)。 如果應用程式從使用者取得遊戲對等連線 HostName 的輸入,則建構函式應該位於 try/catch 區塊中。 如果發生例外狀況,應用程式可通知使用者並要求新的主機名稱。

新增程式碼以驗證使用者主機名稱的字串

// 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 命名空間具有方便的協助程式方法和列舉,可用來處理使用通訊端時的錯誤。 對於在應用程式中以不同方式處理特定網路例外狀況大有幫助。

DatagramSocketStreamSocketStreamSocketListener 作業上發生錯誤,會導致擲回例外狀況。 例外狀況的原因是錯誤值,以 HRESULT 值表示。 SocketError.GetStatus 方法可用來將網路錯誤從通訊端作業轉換成 SocketErrorStatus 列舉值。 大多數 SocketErrorStatus 列舉值對應於原生 Windows 通訊端作業傳回的錯誤。 應用程式可以篩選特定 SocketErrorStatus 列舉值,依據例外狀況的發生原因來修改應用程式行為。

針對參數驗證錯誤,應用程式也可使用來自例外狀況的 HRESULT,深入瞭解造成例外狀況之錯誤的詳細資訊。 可能的 HRESULT 值會列在 Winerror.h 標頭檔中。 針對大多數的參數驗證錯誤,傳回的 HRESULTE_INVALIDARG

新增程式碼以在嘗試建立串流通訊端連線時處理例外狀況

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

Windows.Web.Http 中的例外狀況

Windows::Web::Http::HttpClient 搭配使用的 Windows::Foundation::Uri 類別的建構函式,如果傳遞的字串不是有效的 URI,則可能會擲回例外狀況 (包含 URI 中不允許的字元)。 在 C++ 中,沒有方法可嘗試將字串剖析為 URI。 如果應用程式取得使用者對 Windows.Foundation.Uri 的輸入,則建構函式應該位於 try/catch 區塊中。 如果發生例外狀況,應用程式可通知使用者並要求新的 URI。

您的應用程式也應該檢查 URI 中的配置是否為 HTTP 或 HTTPS,因為這些配置是 Windows::Web::Http::HttpClient 唯一支援的配置。

新增程式碼以驗證使用者 URI 的字串

    // 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 命名空間缺乏便利函式。 因此,使用此命名空間中 HttpClient 和其他類別的應用程式需要使用 HRESULT 值。

在使用 C++ 的應用程式中,Platform::Exception 代表應用程式執行期間發生例外狀況時的錯誤。 Platform::Exception::HResult 屬性會傳回指派給特定例外狀況的 HRESULTPlatform::Exception::Message 屬性傳回系統提供的與 HRESULT 值相關聯的字串。 可能的 HRESULT 值會列在 Winerror.h 標頭檔中。 應用程式可以篩選特定 HRESULT 值,依據例外狀況的發生原因來修改應用程式行為。

針對大多數的參數驗證錯誤,傳回的 HRESULTE_INVALIDARG。 針對某些不正確的方法呼叫,傳回的 HRESULTE_ILLEGAL_METHOD_CALL

新增程式碼來處理嘗試使用 HttpClient 連線到 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;
            }
        }
    });

其他資源

參考

範例應用程式