Partager via


Handle network exceptions in your DirectX game

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Learn how to handle network exceptions when using networking APIs in your DirectX game.

When a network exception occurs in your DirectX game, this indicates a significant problem or failure. 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.

Some causes of exceptions when using networking APIs include the following:

  • Input from the user for a hostname or a URI contains errors and is not valid.
  • Name resolutions failures when looking up a hostname or a URi.
  • Loss or change in network connectivity.
  • Network connection failures using sockets or the HTTP client APIs.
  • Network server or remote endpoint errors.
  • Miscellaneous networking errors.

Exceptions from network errors (loss or change of connectivity, connection failures, and server failures, for example) can happen at any time. These errors result in exceptions being thrown. If not handled by your app, an exception 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 Store apps generally throw a single exception. Your exception handler can retrieve more detailed information on the cause of the exception to better understand the failure and make appropriate decisions.

When an exception occurs in a DirectX game that is a Windows Store app , the HRESULT value for the cause of the error can be retrieved. The Winerror.h include file contains a large list of possible HRESULT values that includes network errors.

The networking APIs support different methods for retrieving this detailed information on the cause of an exception.

  • A method to retrieve the HRESULT value of the error that caused the exception. The possible list of potential HRESULT values is large and unspecified. The HRESULT value can be retrieved when using any of the networking APIs.
  • A helper method that converts the HRESULT value to an enumeration value. The list of possible enumeration values is specified and relatively small. A helper method is available for the socket classes in the Windows::Networking::Sockets.

Exceptions in Windows.Networking.Sockets

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

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.

An error encountered on DatagramSocket, StreamSocket, or StreamSocketListener operation results in an exception being thrown. The cause of the exception is an error value represented as an HRESULT value. The SocketError.GetStatus method is used to convert a network error from a socket operation to a SocketErrorStatus enumeration value. Most of the SocketErrorStatus enumeration values correspond to an error returned by the native Windows sockets operation. An app can filter on specific SocketErrorStatus enumeration values to modify app behavior depending on the cause of the exception.

For parameter validation errors, an app can also use the HRESULT from the exception to learn more detailed information on the error that caused the exception. Possible HRESULT values are listed in the Winerror.h header file. For 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;

    // How many times have we tried to connect the socket
    unsigned int retryConnectCount = 0;

    // 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, the mere 
            // existance if 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 hostname from user, this may indicate bad input
                        // set a flag to ask user to re-enter hostname
                        isHostnameValid = false;
                        return;
                        break;
                    case ConnectionRefused:
                        // The server might be temporarily busy
                        retrySocketConnect = true;
                        return;
                        break; 
                    case NetworkIsUnreachable: 
                        // Could be a connectivity issue
                        retrySocketConnect = true;
                        break;
                    case UnreachableHost: 
                        // Could be a connectivity issue
                        retrySocketConnect = true;
                        break;
                    case NetworkIsDown: 
                        // Could be a connectivity issue
                        retrySocketConnect = true;
                        break;
                    // handle other errors 
                    default: 
                        // Connection failed and no options are available
                        // Try to use cached data if available 
                        // may want to tell user that connect failed
                        break;
                }
                }
                else 
                {
                    // got an Hresult that is not mapped to an enum
                    // Could be a connectivity issue
                    retrySocketConnect = true;
                }
            }
        });
    }

Exceptions in Windows.Web.Http

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). In C++, there is no method to try and parse a string to a URI. If an app gets input from the user for the Windows::Foundation::Uri, the constructor should be in a try/catch block. If an exception is thrown, the app can notify the user and request a new URI.

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.

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

The Windows::Web::Http namespace lacks a convenience function. So an app using HttpClient and other classes in this namespace needs to use the HRESULT value.

In apps using C++, the Platform::Exception represents an error during app execution when an exception occurs. The Platform::Exception::HResult property returns the HRESULT assigned to the specific exception. The Platform::Exception::Message property returns the system-provided string that is associated with the HRESULT value. Possible HRESULT values are listed in the Winerror.h header file. An app can filter on specific HRESULT values to modify app behavior depending on the cause of the exception.

For most parameter validation errors, the HRESULT returned is E_INVALIDARG. For some illegal method calls, the HRESULT returned is E_ILLEGAL_METHOD_CALL.

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;

    // How many times have we tried to connect the socket
    unsigned int retryConnectCount = 0;

    // 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, the mere 
            // existance if 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 Uri from user, this may indicate bad input
                    // set a flag to ask user to re-enter 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: 
                    // Could be a connectivity issue
                    retryHttpClientConnect = true;
                    break;
                case WININET_E_CONNECTION_RESET: 
                    // Could be a connectivity issue
                    retryHttpClientConnect = true;
                    break;
                case INET_E_RESOURCE_NOT_FOUND: 
                    // Server cannot locate the resource specified in the uri
                    // If Uri from user, this may indicate bad input
                    // set a flag to ask user to re-enter Uri
                    isUriValid = false;
                    return;
                    break;
                // handle other errors 
                default: 
                    // Connection failed and no options are available
                    // Try to use cached data if available 
                    // may want to tell user that connect failed
                    break;
            }
            else 
            {
                // got an Hresult that is not mapped to an enum
                // Could be a connectivity issue
                retrySocketConnect = true;
            }
        }
    });
    

Other resources

Adding support for networking

Connecting with a datagram socket

Connecting to a network resource with a stream socket

Connecting to network services

Connecting to web services

Handling exceptions in network apps

How to configure network isolation capabilities

Staying connected in the background

Troubleshooting and debugging network connections

Work with networking in your DirectX game

Reference

DatagramSocket

HttpClient

StreamSocket

Windows::Foundation::Uri

Windows::Networking::Sockets

Windows::Web::Http