游戏网络Networking for games

了解如何在你的 DirectX 游戏中开发并融入联网功能。Learn how to develop and incorporate networking features into your DirectX game.

概念概览Concepts at a glance

无论你的 DirectX 游戏是简单的独立游戏,还是大规模多玩家游戏,都可以使用各种各样的网络功能。A variety of networking features can be used in your DirectX game, whether it is a simple standalone game to massively multi-player games. 对网络最简单的利用就是将用户名和游戏得分存储在中心网络服务器上。The simplest use of networking would be to store user names and game scores on a central network server.

使用基础结构(客户端服务器或 Internet 对等)模型的多玩家游戏和临时(本地对等)游戏都需要网络 API。Networking APIs are needed in multi-player games that use the infrastructure (client-server or internet peer-to-peer) model and also by ad hoc (local peer-to-peer) games. 对于基于服务器的多玩家游戏,中心游戏服务器通常处理大部分游戏操作,而客户端游戏应用用于输入、显示图形、播放音频以及其他功能。For server-based multi-player games, a central game server usually handles most of the game operations and the client game app is used for input, displaying graphics, playing audio, and other features. 要提供令人满意的游戏体验,需要考虑网络传输的速度和延迟。The speed and latency of network transfers is a concern for a satisfactory game experience.

对于对等游戏,每个玩家的应用都会处理输入和图形。For peer-to-peer games, each player's app handles the input and graphics. 在大多数情况下,游戏玩家位于邻近位置,因此网络延迟应该会降低,但仍需要考虑这个问题。In most cases, the game players are located in close proximity so that network latency should be lower but is still a concern. 需要考虑如何发现对等方并建立连接。How to discovery peers and establish a connection becomes a concern.

对于单个玩家的游戏,中心 Web 服务器或服务通常用于存储用户名、游戏得分和其他信息。For single-player games, a central Web server or service is often used to store user names, game scores, and other miscellaneous information. 在这些游戏中,不需要过多考虑网络传输的速度和延迟,因为它不会直接影响游戏操作。In these games, the speed and latency of networking transfers is less of a concern since it doesn't directly affect game operation.

由于网络条件可能随时发生更改,因此任何使用网络 API 的游戏都需要处理可能发生的网络异常。Network conditions can change at any time, so any game that uses networking APIs needs to handle network exceptions that may occur. 若要了解有关处理网络异常的详细信息,请参阅网络基础知识To learn more about handling network exceptions, see Networking basics.

防火墙和 Web 代理十分常见,它们可以影响使用网络功能的能力。Firewalls and web proxies are common and can affect the ability to use networking features. 需要为使用网络的游戏做好准备,使其正确处理防火墙和代理。A game that uses networking needs to be prepared to properly handle firewalls and proxies.

对于移动设备,重要的是在按流量计费的网络上监视可用的网络资源,并执行相应的操作;在按流量计费的网络上可能发生大量漫游或产生大量数据成本。For mobile devices, it is important to monitor available network resources and behave accordingly when on metered networks where roaming or data costs can be significant.

网络隔离是 Windows 所使用的应用安全模型的组成部分。Network isolation is part of the app security model used by Windows. Windows 会主动发现网络边界,并为网络隔离强制实现网络访问限制。Windows actively discovers network boundaries and enforces network access restrictions for network isolation. 为了定义网络访问范围,应用必须声明网络隔离功能。Apps must declare network isolation capabilities in order to define the scope of network access. 如果没有声明这些功能,你的应用将不能访问网络资源。Without declaring these capabilities, your app will not have access to network resources. 若要 了解有关 Windows 如何为应用强制执行网络隔离的详细信息,请参阅如何配置网络隔离功能To learn more about how Windows enforces network isolation for apps, see How to configure network isolation capabilities.

设计注意事项Design considerations

在 DirectX 游戏中可以使用各种各样的网络 API。A variety of networking APIs can be used in DirectX games. 因此,选择正确的 API 非常重要。So, it is important to pick the right API. Windows 支持各种各样的网络 API, 你的应用可以使用它们通过 Internet 或专用网络与其他计算机和设备进行通信。Windows supports a variety of networking APIs that your app can use to communicate with other computers and devices over either the Internet or private networks. 第一个步骤是确定你的应用需要哪些网络功能。Your first step is to figure out what networking features your app needs.

这些是更常用的游戏网络 Api。These are the more popular network APIs for games.

  • TCP 和套接字 – 提供可靠连接。TCP and sockets - Provides a reliable connection. 可以将 TCP 用于不需要安全性的游戏操作。Use TCP for game operations that don’t need security. 因为 TCP 允许服务器轻松缩放,所以它通常用于使用基础结构(客户端服务器或 Internet 对等)模型的游戏。TCP allows the server to easily scale, so it is commonly used in games that use the infrastructure (client-server or internet peer-to-peer) model. 临时(本地对等)游戏也可以通过 Wi-Fi Direct 和 BlueTooth 使用 TCP。TCP can also be used by ad hoc (local peer-to-peer) games over Wi-Fi Direct and Bluetooth. TCP 通常用于游戏对象移动、角色交互、文本聊天和其他操作。TCP is commonly used for game object movement, character interaction, text chat, and other operations. StreamSocket类提供了可在 Microsoft Store 游戏中使用的 TCP 套接字。The StreamSocket class provides a TCP socket that can be used in Microsoft Store games. StreamSocket 类与 Windows::Networking::Sockets 命名空间中的相关类搭配使用。The StreamSocket class is used with related classes in the Windows::Networking::Sockets namespace.
  • 使用 SSL 的 TCP 和套接字 – 提供防止窃听的可靠连接。TCP and sockets using SSL - Provides a reliable connection that prevents eavesdropping. 将带有 SSL 的 TCP 连接用于需要安全性的游戏操作。Use TCP connections with SSL for game operations that need security. 由于 SSL 的加密和开销将导致延迟并降低性能,所以仅在需要安全性时使用它。The encryption and overhead of SSL adds a cost in latency and performance, so it is only used when security is needed. 带有 SSL 的 TCP 通常用于登录、购买和交易资源、游戏角色创建和管理。TCP with SSL is commonly used for login, purchasing and trading assets, game character creation and management. StreamSocket 类提供支持 SSL 的 TCP 套接字。The StreamSocket class provides a TCP socket that supports SSL.
  • UDP 和套接字 – 提供具有较低开销的不可靠网络传输。UDP and sockets - Provides unreliable network transfers with low overhead. UDP 用于要求较少延迟,但可以容忍一些数据包丢失的游戏操作。UDP is used for game operations that require low latency and can tolerate some packet loss. 它经常用于打斗游戏、射击和跟踪、网络音频以及语音聊天。This is often used for fighting games, shooting and tracers, network audio, and voice chat. DatagramSocket类提供了可在 Microsoft Store 游戏中使用的 UDP 套接字。The DatagramSocket class provides a UDP socket that can be used in Microsoft Store games. DatagramSocket 类与 Windows::Networking::Sockets 命名空间中的相关类搭配使用。The DatagramSocket class is used with related classes in the Windows::Networking::Sockets namespace.
  • HTTP 客户端 – 提供到 HTTP 服务器的可靠连接。HTTP Client - Provides a reliable connection to HTTP servers. 最常见的网络方案是访问网站来检索或存储信息。The most common networking scenario is to access a web site to retrieve or store information. 一个简单的示例是使用网站来存储用户信息和游戏得分的游戏。A simple example would be a game that uses a website to store user information and game scores. 当与 SSL 一起使用来确保安全性时,HTTP 客户端可以用于登录、购买、交易资源、游戏角色创建以及管理。When used with SSL for security, an HTTP client can be used for login, purchasing, trading assets, game character creation, and management. HttpClient类提供了新式 HTTP 客户端 API,可用于 Microsoft Store 游戏。The HttpClient class provides a modern HTTP client API for use in Microsoft Store games. HttpClient 类与 Windows::Web::Http 命名空间中的相关类搭配使用。The HttpClient class is used with related classes in the Windows::Web::Http namespace.

在 DirectX 游戏中处理网络异常Handling network exceptions in your DirectX game

当 DirectX 游戏中发生网络异常时,这表示存在重大问题或故障。When a network exception occurs in your DirectX game, this indicates a significant problem or failure. 使用网络 API 时,许多原因会导致发生异常。Exceptions can occur for many reasons when using networking APIs. 使用远程主机或服务器时,网络连接发生更改或其他网络问题通常会导致异常。Often, the exception can result from changes in network connectivity or other networking issues with the remote host or server.

使用网络 API 时导致异常的一些原因包括以下几条:Some causes of exceptions when using networking APIs include the following:

  • 用户输入的主机名或 URI 包含错误而且无效。Input from the user for a hostname or a URI contains errors and is not valid.
  • 在查找主机名或 URI 时名称解析失败。Name resolutions failures when looking up a hostname or a URi.
  • 网络连接中断或发生更改。Loss or change in network connectivity.
  • 使用套接字或 HTTP 客户端 API 连接网络失败Network connection failures using sockets or the HTTP client APIs.
  • 网络服务器或远程终结点错误。Network server or remote endpoint errors.
  • 其他网络错误。Miscellaneous networking errors.

随时可能由网络错误(例如,连接中断或发生更改、连接失败和服务器失败)引发异常。Exceptions from network errors (for example, loss or change of connectivity, connection failures, and server failures) can happen at any time. 这些错误将引发异常。These errors result in exceptions being thrown. 如果应用不处理异常,它可能导致整个应用在运行时终止。If an exception is not handled by your app, it can cause your entire app to be terminated by the runtime.

当你调用大部分异步网络方法时, 必须编写代码以处理异常。You must write code to handle exceptions when you call most asynchronous network methods. 有时,在发生异常时,可以重试网络方法来解决问题。Sometimes, when an exception occurs, a network method can be retried as a way to resolve the problem. 在其他时候,应用可能需要计划使用之前的缓存数据在没有网络连接的情况下继续工作。Other times, your app may need to plan to continue without network connectivity using previously cached data.

通用 Windows 平台 (UWP) 应用通常引发单个异常。Universal Windows Platform (UWP) apps generally throw a single exception. 异常处理程序可以检索有关异常原因的更详细信息,以更好地了解此次失败,并作出适当的决策。Your exception handler can retrieve more detailed information about the cause of the exception to better understand the failure and make appropriate decisions.

当在属于 UWP 应用的 DirectX 游戏中发生异常时,可以检索表示错误原因的 HRESULT 值。When an exception occurs in a DirectX game that is a UWP app, the HRESULT value for the cause of the error can be retrieved. Winerror.h include file 包含一个 HRESULT 可能值的大型列表,其中包括网络错误。The Winerror.h include file contains a large list of possible HRESULT values that includes network errors.

网络 API 支持使用不同方法来检索有关异常原因的详细信息。The networking APIs support different methods for retrieving this detailed information about the cause of an exception.

  • 用于检索导致异常的错误的 HRESULT 值的方法。A method to retrieve the HRESULT value of the error that caused the exception. 可能的 HRESULT 值的列表较大且尚未指定。The possible list of potential HRESULT values is large and unspecified. 当使用任何网络 API 时,可以检索 HRESULT 值。The HRESULT value can be retrieved when using any of the networking APIs.
  • 可以将 HRESULT 值转换为枚举值的帮助程序方法。A helper method that converts the HRESULT value to an enumeration value. 已指定可能枚举值的列表,该列表相对较小。The list of possible enumeration values is specified and relatively small. 帮助程序方法可用于 Windows::Networking::Sockets 中的套接字类。A helper method is available for the socket classes in the Windows::Networking::Sockets.

Windows.Networking.Sockets 中的异常Exceptions in Windows.Networking.Sockets

如果传递的字符串不是有效的主机名(包含主机名中不允许的字符),则与套接字一起使用的 HostName 类的构造函数会引发异常。The constructor for the HostName class used with sockets can throw an exception if the string passed is not a valid hostname (contains characters that are not allowed in a host name). 如果应用从用户处获取了游戏对等方连接的 HostName 输入,则构造函数应位于 try/catch 块中。If an app gets input from the user for the HostName for a peer connection for gaming, the constructor should be in a try/catch block. 如果引发了异常,该应用可以通知用户并请求新的主机名。If an exception is thrown, the app can notify the user and request a new hostname.

添加代码以验证用户输入的用于主机的字符串Add code to validate a string for a hostname from the user

// 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 命名空间具有方便的帮助程序方法和枚举,以便在使用套接字时处理错误。The Windows.Networking.Sockets namespace has convenient helper methods and enumerations for handling errors when using sockets. 这有助于在应用中分别处理特定网络异常。This can be useful for handling specific network exceptions differently in your app.

DatagramSocketStreamSocketStreamSocketListener 操作上发生的错误将引发异常。An error encountered on DatagramSocket, StreamSocket, or StreamSocketListener operation results in an exception being thrown. 异常原因是一个错误值,表示为 HRESULT 值。The cause of the exception is an error value represented as an HRESULT value. SocketError.GetStatus 方法用于将来自套接字操作的网络错误转换为 SocketErrorStatus 枚举值。The SocketError.GetStatus method is used to convert a network error from a socket operation to a SocketErrorStatus enumeration value. 大部分 SocketErrorStatus 枚举值对应由本机 Windows 套接字操作返回的错误。Most of the SocketErrorStatus enumeration values correspond to an error returned by the native Windows sockets operation. 应用可以筛选特定 SocketErrorStatus 枚举值来基于异常原因修改应用行为。An app can filter on specific SocketErrorStatus enumeration values to modify app behavior depending on the cause of the exception.

对于参数验证错误,应用还可以使用来自异常的 HRESULT 了解有关导致该异常的错误的更详细信息。For parameter validation errors, an app can also use the HRESULT from the exception to learn more detailed information about the error that caused the exception. 可能的 HRESULT 值将在 Winerror.h 头文件中列出。Possible HRESULT values are listed in the Winerror.h header file. 对于大多数参数验证错误,返回的 HRESULTE _ INVALIDARGFor most parameter validation errors, the HRESULT returned is E_INVALIDARG.

尝试连接流套接字时,添加处理异常的代码Add code to handle exceptions when trying to make a stream socket connection

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 中的异常Exceptions in Windows.Web.Http

如果传递的字符串不是有效的 URI(包含 URI 中不允许的字符),则与 Windows::Web::Http::HttpClient 一起使用的 Windows::Foundation::Uri 类的构造函数会引发异常。The constructor for the Windows::Foundation::Uri class used with Windows::Web::Http::HttpClient can throw an exception if the string passed is not a valid URI (contains characters that are not allowed in a URI). 在 C++ 中,没有可用于试用字符串和将其解析到 URI 的方法。In C++, there is no method to try and parse a string to a URI. 如果应用获取的用户输入提供给 Windows::Foundation::Uri,则构造函数应位于 try/catch 块中。If an app gets input from the user for the Windows::Foundation::Uri, the constructor should be in a try/catch block. 如果引发了异常,该应用可以通知用户并请求新的 URI。If an exception is thrown, the app can notify the user and request a new URI.

因为 Windows::Web::Http::HttpClient 仅支持 HTTP 和 HTTPS 架构,所以你的应用还应该检查 URI 中的架构是 HTTP 还是 HTTPS。Your app should also check that the scheme in the URI is HTTP or HTTPS since these are the only schemes supported by the Windows::Web::Http::HttpClient.

添加代码以验证用户输入的用于 URI 的字符串Add code to validate a string for a URI from the user

    // 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 命名空间缺少方便函数。The Windows::Web::Http namespace lacks a convenience function. 因此,使用 HttpClient 的应用和此命名空间中的其他类需要使用 HRESULT 值。So, an app using HttpClient and other classes in this namespace needs to use the HRESULT value.

在使用 C++ 的应用中,发生异常时,Platform::Exception 表示应用执行期间的错误。In apps using C++, the Platform::Exception represents an error during app execution when an exception occurs. Platform:: Exception:: HResult属性返回分配给特定异常的hresultThe Platform::Exception::HResult property returns the HRESULT assigned to the specific exception. Platform:: Exception:: Message属性返回与HRESULT值关联的系统提供的字符串。The Platform::Exception::Message property returns the system-provided string that is associated with the HRESULT value. 可能的 HRESULT 值将在 Winerror.h 头文件中列出。Possible HRESULT values are listed in the Winerror.h header file. 应用可以筛选特定 HRESULT 值来根据异常原因修改应用行为。An app can filter on specific HRESULT values to modify app behavior depending on the cause of the exception.

对于大多数参数验证错误,返回的 HRESULTE _ INVALIDARGFor most parameter validation errors, the HRESULT returned is E_INVALIDARG. 对于某些非法的方法调用,返回的 HRESULTE_ILLEGAL_METHOD_CALLFor some illegal method calls, the HRESULT returned is E_ILLEGAL_METHOD_CALL.

当尝试使用 HttpClient 连接到 HTTP 服务器时,添加处理异常的代码Add code to handle exceptions when trying to use HttpClient to connect to an HTTP server

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

其他资源Other resources

参考Reference

示例应用Sample apps