Conexión en red de juegos
Aprende a desarrollar e incorporar características de red en un juego de DirectX.
Conceptos de un vistazo
En tu juego de DirectX puede usar una variedad de características de red, ya sea un sencillo juego independiente o juegos masivos de varios jugadores. El uso más sencillo de las redes sería para almacenar nombres de usuario y puntuaciones de juego en un servidor de red central.
En los juegos de varios jugadores que usan el modelo de infraestructura (cliente-servidor o de punto a punto en Internet) y también en los juegos ad hoc (de punto a punto locales), se necesitan API de red. En el caso de los juegos de varios jugadores basados en servidor, un servidor de juegos central normalmente controla la mayoría de las operaciones del juego y la aplicación de juego cliente se usa para la entrada, la visualización de gráficos, la reproducción de audio y otras características. La velocidad y la latencia de las transferencias de red son factores importantes para tener una experiencia de juego satisfactoria.
En el caso de los juegos punto a punto, la aplicación de cada jugador controla la entrada y los gráficos. En la mayoría de los casos, los jugadores se encuentran cerca para que la latencia de red sea menor, aunque siga siendo un aspecto que tener en cuenta. La forma de detectar a otros jugadores y de establecer una conexión se convierte en un problema.
En el caso de los juegos de un solo jugador, normalmente se usa un servidor web central o un servicio para almacenar nombres de usuario, puntuaciones de juego y otra información. En estos juegos, la velocidad y la latencia de las transferencias de red es menos preocupante, ya que no afecta directamente al funcionamiento del juego.
Las condiciones de red pueden cambiar en cualquier momento, por lo que cualquier juego que use las API de red debe controlar las excepciones de red que se puedan producir. Para más información sobre el control de excepciones de red, vea Conceptos básicos de redes.
Los firewalls y servidores proxy web son comunes y pueden afectar a la capacidad de usar las características de red. Un juego que use redes debe estar preparado para controlar correctamente los firewalls y servidores proxy.
En el caso de los dispositivos móviles, es importante supervisar los recursos de red disponibles y comportarse en consecuencia en las redes de uso medido, en las que los costos de datos o la itinerancia pueden ser significativos.
El aislamiento de la red forma parte del modelo de seguridad de aplicaciones usado por Windows. Windows detecta activamente los límites de red y aplica restricciones de acceso para el aislamiento de la red. Las aplicaciones deben declarar funcionalidades de aislamiento de red para definir el ámbito de acceso a la red. Si no se declaran estas funcionalidades, la aplicación no tendrá acceso a los recursos de red. Para más información sobre cómo Windows aplica el aislamiento de red para las aplicaciones, vea Configuración de funcionalidades de aislamiento de red.
Consideraciones de diseño
En los juegos de DirectX se pueden usar varias API de red. Por tanto, es importante elegir la API correcta. Windows admite una variedad de API de red que la aplicación puede usar para comunicarse con otros equipos y dispositivos a través de Internet o redes privadas. El primer paso consiste en es determinar qué características de red necesita la aplicación.
Estas son las API de red más populares para juegos.
- TCP y sockets: proporciona una conexión confiable. Usa TCP para las operaciones de juego que no necesiten seguridad. TCP permite al servidor modificar la escala fácilmente, por lo que habitualmente se utiliza en juegos que usan el modelo de infraestructura (servidor cliente o punto a punto por Internet). TCP también se puede usar en juegos ad hoc (de punto a punto locales) a través de Wi-Fi Direct y Bluetooth. TCP se usa normalmente para el movimiento de objetos del juego, la interacción de los personajes, el chat de texto y otras operaciones. La clase StreamSocket proporciona un socket TCP que se puede usar en juegos de Microsoft Store. La clase StreamSocket se usa con clases relacionadas del espacio de nombres Windows::Networking::Sockets.
- TCP y sockets mediante SSL: proporciona una conexión confiable que impide la interceptación. Usa conexiones TCP con SSL para las operaciones del juego que necesiten seguridad. El cifrado y la sobrecarga de SSL aumentan el costo de latencia y rendimiento, por lo que solo se usa cuando se necesita seguridad. TCP con SSL se usa habitualmente para el inicio de sesión, la compra y el intercambio de activos, y la creación y administración de personajes del juego. La clase StreamSocket proporciona un socket TCP que admite SSL.
- UDP y sockets: proporciona transferencias de red no confiables con poca sobrecarga. UDP se usa para las operaciones del juego que necesitan baja latencia y pueden tolerar cierta pérdida de paquetes. Esto se usa a menudo en juegos de lucha, de disparos y seguimientos, audio de red y chat de voz. La clase DatagramSocket proporciona un socket UDP que se puede usar en juegos de Microsoft Store. La clase DatagramSocket se usa con clases relacionadas del espacio de nombres Windows::Networking::Sockets.
- Cliente HTTP: proporciona una conexión confiable a servidores HTTP. El escenario de red más común consiste en acceder a un sitio web para recuperar o almacenar información. Un ejemplo sencillo sería un juego que usa un sitio web para almacenar información de los usuarios y puntuaciones del juego. Cuando se usa con SSL para la seguridad, se puede utilizar un cliente HTTP para el inicio de sesión, compras, el intercambio de activos, la creación de personajes del juego y la administración. La clase HttpClient proporciona una moderna API cliente HTTP que se puede usar en juegos de Microsoft Store. La clase HttpClient se usa con clases relacionadas en el espacio de nombres Windows::Web::Http.
Control de excepciones de red en el juego de DirectX
Cuando se produce una excepción de red en el juego de DirectX, esto indica un problema o un error significativos. Las excepciones se pueden producir por muchos motivos al usar las API de red. A menudo, la excepción puede deberse a cambios en la conectividad de red u otros problemas de red con el host remoto o el servidor.
Entre algunas de las causas de las excepciones al usar las API de red se incluyen las siguientes:
- La entrada del usuario para un nombre de host o un URI contiene errores y no es válida.
- Errores de resolución de nombres al buscar un nombre de host o un URI.
- Pérdida o cambio en la conectividad de red.
- Errores de conexión de red al usar sockets o las API de cliente HTTP.
- Errores de servidor de red o punto de conexión remoto.
- Errores de red varios.
Las excepciones de errores de red (por ejemplo, de pérdida o cambio de conectividad, de conexión y de servidor) se pueden producir en cualquier momento. Estos errores provocan que se inicien excepciones. Si la aplicación no controla una excepción, puede hacer que el tiempo de ejecución finalice toda la aplicación.
Debes escribir código para controlar las excepciones cuando llamas a la mayoría de los métodos de red asincrónicos. A veces, cuando se produce una excepción, se puede reintentar un método de red como una manera de resolver el problema. En otras ocasiones, es posible que la aplicación tenga que planear continuar sin conectividad de red y usar datos almacenados en caché previamente.
Las aplicaciones de la Plataforma universal de Windows (UWP) suelen iniciar una única excepción. El controlador de excepciones puede recuperar información más detallada sobre la causa de la excepción para que así puedas comprender mejor el error y tomar las decisiones adecuadas.
Cuando se produce una excepción en un juego de DirectX que es una aplicación para UWP, se puede recuperar el valor HRESULT de la causa del error. El archivo de inclusión Winerror.h contiene una lista muy extensa de valores HRESULT posibles, que incluye los errores de red.
Las API de red admiten distintos métodos para recuperar esta información que detalla la causa de una excepción.
- Un método para recuperar el valor HRESULT del error que ha provocado la excepción. La posible lista de valores HRESULT es extensa y sin especificar. El valor HRESULT se puede recuperar al usar cualquiera de las API de red.
- Un método auxiliar que convierte el valor HRESULT en un valor de enumeración. La lista de posibles valores de enumeración se especifica y es relativamente reducida. Hay disponible un método auxiliar para las clases de socket de la Windows::Networking::Sockets.
Excepciones en Windows.Networking.Sockets
El constructor de la clase HostName usada con los sockets puede iniciar una excepción si la cadena pasada no es un nombre de host válido (contiene caracteres no permitidos en un nombre de host). Si una aplicación obtiene la entrada del usuario relativa a HostName de una conexión del mismo nivel, el constructor debe estar en un bloque try/catch. Si se genera una excepción, la aplicación podrá notificar al usuario y solicitar otro nombre de host.
Adición de código a fin de validar una cadena para un nombre de host del usuario
// 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.
El espacio de nombres Windows.Networking.Sockets tiene métodos auxiliares prácticos y enumeraciones para controlar errores al usar sockets. Esto puede ser útil para controlar de un modo diferente excepciones de red específicas en la aplicación.
Un error detectado en una operación DatagramSocket, StreamSocket o StreamSocketListener hace que se inicie una excepción. La causa de la excepción es un valor de error representado como un valor HRESULT. El método SocketError.GetStatus se usa para convertir un error de red de una operación de socket en un valor de enumeración SocketErrorStatus. La mayoría de los valores de enumeración SocketErrorStatus se corresponden a un error devuelto por la operación de sockets de Windows nativa. Una aplicación puede filtrar valores de enumeración SocketErrorStatus concretos para modificar el comportamiento en función de la causa de la excepción.
Para los errores de validación de parámetros, una aplicación también puede usar el valor HRESULT de la excepción para obtener información más detallada sobre el error que la ha provocado. Los valores posibles de HRESULT se enumeran en el archivo de encabezado Winerror.h. Para la mayoría de los errores de validación de parámetros, el valor de HRESULT que se devuelve es E_INVALIDARG.
Adición de código para controlar excepciones al intentar establecer una conexión de socket de flujo
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;
}
}
});
}
Excepciones en Windows.Web.Http
El constructor de la clase Windows::Foundation::Uri que se usa con Windows::Web::Http::HttpClient puede iniciar una excepción si la cadena pasada no es un URI válido (contiene caracteres no permitidos en un URI). En C++, no hay ningún método para probar y analizar una cadena en un URI. Si una aplicación obtiene la entrada del usuario relativa a Windows::Foundation::Uri, el constructor debe estar en un bloque try/catch. Si se inicia una excepción, la aplicación puede notificar al usuario y solicitar un nuevo URI.
La aplicación también debe comprobar que el esquema del URI es HTTP o HTTPS, ya que estos son los únicos que admite Windows::Web::Http::HttpClient.
Adición de código a fin de validar una cadena para un URI del usuario
// 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.
El espacio de nombres Windows::Web::Http carece de una función adecuada. Por este motivo, una aplicación que use HttpClient y otras clases de este espacio de nombres debe utilizar el valor HRESULT.
En las aplicaciones que usan C++, Platform::Exception representa un error durante la ejecución de la aplicación cuando se produce una excepción. La propiedad Platform::Exception::HResult devuelve el valor HRESULT asignado a la excepción concreta. La propiedad Platform::Exception::Message devuelve la cadena proporcionada por el sistema asociada con el valor HRESULT. Los valores posibles de HRESULT se enumeran en el archivo de encabezado Winerror.h. Una aplicación puede filtrar por valores HRESULT concretos para modificar su comportamiento en función del motivo de la excepción.
Para la mayoría de los errores de validación de parámetros, el valor de HRESULT que se devuelve es E_INVALIDARG. Para algunas llamadas a métodos no válidas, el valor de HRESULT devuelto es E_ILLEGAL_METHOD_CALL.
Adición de código para controlar excepciones al intentar usar HttpClient para conectarse a un servidor 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;
}
}
});
Temas relacionados
Otros recursos
- Conexión con un socket de datagrama
- Conexión a un recurso de red con un socket de flujo
- Conexión a servicios de red
- Conexión a servicios web
- Conceptos básicos de redes
- Configuración de funcionalidades de aislamiento de red
- Habilitación del aislamiento de red de bucle invertido y depuración
Referencia
- DatagramSocket
- HttpClient
- StreamSocket
- Espacio de nombres Windows::Web::Http
- Espacio de nombres Windows::Networking::Socket