Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Para continuar la comunicación de red mientras no está en primer plano, la aplicación puede usar tareas en segundo plano y una de estas dos opciones.
- Agente de socket. Si la aplicación usa sockets para conexiones a largo plazo, cuando sale del primer plano, puede delegar la propiedad de un socket a un agente de sockets del sistema. A continuación, el intermediario: activa tu aplicación cuando llega tráfico al socket; devuelve el control a tu aplicación; y tu aplicación procesa el tráfico entrante.
- Desencadenadores del canal de control.
Realización de operaciones de red en tareas en segundo plano
- Usa un SocketActivityTrigger para activar la tarea en segundo plano cuando se recibe un paquete y necesitas realizar una tarea de corta duración. Después de realizar la tarea, la tarea en segundo plano debe finalizar para ahorrar energía.
- Use un ControlChannelTrigger para activar la tarea en segundo plano cuando se recibe un paquete y necesite realizar una tarea de larga duración.
Condiciones y marcas relacionados con la red
- Agregue la condición InternetAvailable a la tarea en segundo plano BackgroundTaskBuilder.AddCondition para retrasar el desencadenamiento de la tarea en segundo plano hasta que se ejecute la pila de red. Esta condición ahorra energía porque la tarea en segundo plano no se ejecutará hasta que la red esté actualizada. Esta condición no proporciona activación en tiempo real.
Independientemente del desencadenador que use, establezca IsNetworkRequested en la tarea en segundo plano para asegurarse de que la red permanece activa mientras se ejecuta la tarea en segundo plano. Esto indica a la infraestructura de tareas en segundo plano que mantenga la red activa mientras se ejecuta la tarea, incluso si el dispositivo ha entrado en modo de espera conectado. Si la tarea en segundo plano no usa IsNetworkRequested, la tarea en segundo plano no podrá acceder a la red cuando esté en modo de espera conectado (por ejemplo, cuando la pantalla de un teléfono está desactivada).
Intermediario de socket y SocketActivityTrigger
Si la aplicación usa conexiones DatagramSocket, StreamSocket o StreamSocketListener , debe usar SocketActivityTrigger y el agente de socket para recibir notificaciones cuando llegue el tráfico a la aplicación mientras no está en primer plano.
Para que la aplicación reciba y procese los datos recibidos en un socket cuando la aplicación no está activa, la aplicación debe realizar una configuración única en el inicio y, a continuación, transferir la propiedad del socket al agente de socket cuando se realiza la transición a un estado en el que no está activo.
Los pasos de configuración que solo hay que realizar una vez son crear un activador, registrar una tarea en segundo plano para el activador y habilitar el socket para el intermediario de sockets:
- Cree un SocketActivityTrigger y registre una tarea en segundo plano para el desencadenador con el parámetro TaskEntryPoint establecido en el código para procesar un paquete recibido.
var socketTaskBuilder = new BackgroundTaskBuilder();
socketTaskBuilder.Name = _backgroundTaskName;
socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
var trigger = new SocketActivityTrigger();
socketTaskBuilder.SetTrigger(trigger);
_task = socketTaskBuilder.Register();
- Llama al objeto EnableTransferOwnership en el socket, antes de enlazar el socket.
_tcpListener = new StreamSocketListener();
// Note that EnableTransferOwnership() should be called before bind,
// so that tcpip keeps required state for the socket to enable connected
// standby action. Background task Id is taken as a parameter to tie wake pattern
// to a specific background task.
_tcpListener.EnableTransferOwnership(_task.TaskId, SocketActivityConnectedStandbyAction.Wake);
_tcpListener.ConnectionReceived += OnConnectionReceived;
await _tcpListener.BindServiceNameAsync("my-service-name");
Una vez configurado correctamente el socket, cuando la aplicación esté a punto de suspenderse, llame a TransferOwnership en el socket para transferirlo a un agente de socket. El intermediario supervisa el socket y activa tu tarea en segundo plano cuando se reciben datos. En el ejemplo siguiente se incluye una función TransferOwnership de utilidad para realizar la transferencia de sockets StreamSocketListener . (Tenga en cuenta que los distintos tipos de sockets tienen su propio método TransferOwnership , por lo que debe llamar al método adecuado para el socket cuya propiedad se transfiere. Es probable que el código contenga un asistente de TransferOwnership sobrecargado con una implementación para cada tipo de socket que use, de modo que el código OnSuspending siga siendo fácil de leer).
La aplicación transfiere la titularidad de un socket a un agente de sockets y pasa el identificador de la tarea en segundo plano usando el más apropiado de los siguientes métodos:
- Uno de los métodos TransferOwnership en un DatagramSocket.
- Uno de los métodos TransferOwnership en streamSocket.
- Uno de los métodos TransferOwnership en streamSocketListener.
// declare int _transferOwnershipCount as a field.
private async void TransferOwnership(StreamSocketListener tcpListener)
{
await tcpListener.CancelIOAsync();
var dataWriter = new DataWriter();
++_transferOwnershipCount;
dataWriter.WriteInt32(_transferOwnershipCount);
var context = new SocketActivityContext(dataWriter.DetachBuffer());
tcpListener.TransferOwnership(_socketId, context);
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
TransferOwnership(_tcpListener);
deferral.Complete();
}
En el controlador de eventos de la tarea en segundo plano:
- En primer lugar, obtenga un aplazamiento de tareas en segundo plano para que pueda controlar el evento mediante métodos asincrónicos.
var deferral = taskInstance.GetDeferral();
- A continuación, extraiga SocketActivityTriggerDetails de los argumentos del evento y busque el motivo por el que se generó el evento:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
var socketInformation = details.SocketInformation;
switch (details.Reason)
- Si el evento se generó debido a la actividad de socket, cree un objeto DataReader en el socket, cargue el lector de forma asincrónica y, a continuación, use los datos según el diseño de la aplicación. Ten en cuenta que debes devolver la propiedad del socket al agente de sockets para volver a recibir notificaciones de actividad de sockets adicional.
En el siguiente ejemplo, el texto recibido en el socket se muestra en una notificación del sistema.
case SocketActivityTriggerReason.SocketActivity:
var socket = socketInformation.StreamSocket;
DataReader reader = new DataReader(socket.InputStream);
reader.InputStreamOptions = InputStreamOptions.Partial;
await reader.LoadAsync(250);
var dataString = reader.ReadString(reader.UnconsumedBufferLength);
ShowToast(dataString);
socket.TransferOwnership(socketInformation.Id); /* Important! */
break;
- Si se generó el evento porque ha expirado un temporizador de mantenimiento activo, el código debe enviar algunos datos sobre el socket para mantener el socket activo y reiniciar el temporizador de mantenimiento activo. De nuevo, es importante devolver la propiedad del socket al agente de sockets para recibir más notificaciones de eventos:
case SocketActivityTriggerReason.KeepAliveTimerExpired:
socket = socketInformation.StreamSocket;
DataWriter writer = new DataWriter(socket.OutputStream);
writer.WriteBytes(Encoding.UTF8.GetBytes("Keep alive"));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
socket.TransferOwnership(socketInformation.Id); /* Important! */
break;
- Si el evento se generó porque el socket estaba cerrado, vuelve a establecerlo y asegúrate de que después de crear el nuevo socket transfieres su propiedad al agente de sockets. En este ejemplo, el nombre de host y el puerto se almacenan en la configuración local para que se puedan usar para establecer una nueva conexión de socket:
case SocketActivityTriggerReason.SocketClosed:
socket = new StreamSocket();
socket.EnableTransferOwnership(taskInstance.Task.TaskId, SocketActivityConnectedStandbyAction.Wake);
if (ApplicationData.Current.LocalSettings.Values["hostname"] == null)
{
break;
}
var hostname = (String)ApplicationData.Current.LocalSettings.Values["hostname"];
var port = (String)ApplicationData.Current.LocalSettings.Values["port"];
await socket.ConnectAsync(new HostName(hostname), port);
socket.TransferOwnership(socketId);
break;
- No olvide completar el aplazamiento, una vez que haya terminado de procesar la notificación de eventos:
deferral.Complete();
Para obtener un ejemplo completo que muestre el uso de SocketActivityTrigger y el agente de sockets, consulte el ejemplo SocketActivityStreamSocket. La inicialización del socket se realiza en Scenario1_Connect.xaml.cs y la implementación de tareas en segundo plano se encuentra en SocketActivityTask.cs.
Probablemente observes que la muestra llama a TransferOwnership en cuanto crea un nuevo socket o adquiere un socket existente, en lugar de usar el controlador de eventos OnSuspending para llevar a cabo esta opción tal y como se describe en este tema. Esto se debe a que el ejemplo se centra en mostrar SocketActivityTrigger y no usa el socket para ninguna otra actividad mientras se ejecuta. La aplicación probablemente será más compleja y debe usar OnSuspending para determinar cuándo llamar a TransferOwnership.
Desencadenadores del canal de control
En primer lugar, asegúrese de que usa los desencadenadores de canal de control (CCT) correctamente. Si usa conexiones DatagramSocket, StreamSocket o StreamSocketListener , se recomienda usar SocketActivityTrigger. Puede usar los CCT con StreamSocket, pero consumen más recursos y podrían no funcionar en el modo Connected Standby.
Si usa WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient o Windows. Web.Http.HttpClient, debe usar ControlChannelTrigger.
ControlChannelTrigger con WebSockets
Important
La característica descrita en esta sección (ControlChannelTrigger con WebSockets) se admite en Windows SDK versión 10.0.15063.0 y posteriores.
Se aplican algunas consideraciones especiales al usar MessageWebSocket o StreamWebSocket con ControlChannelTrigger. Hay algunos patrones de uso específicos del transporte y procedimientos recomendados que se deben seguir al usar messageWebSocket o StreamWebSocket con ControlChannelTrigger. Además, estas consideraciones afectan a la forma en que se controlan las solicitudes para recibir paquetes en StreamWebSocket . Las solicitudes para recibir paquetes en MessageWebSocket no se ven afectadas.
Se deben seguir los siguientes patrones de uso y procedimientos recomendados al usar MessageWebSocket o StreamWebSocket con ControlChannelTrigger:
- Una recepción de socket pendiente debe estar expuesta en todo momento. Esto es necesario para que puedan ejecutarse las tareas de notificaciones push.
- El protocolo WebSocket define un modelo estándar para los mensajes keep-alive. La clase WebSocketKeepAlive puede enviar mensajes keep-alive del protocolo WebSocket iniciados por el cliente al servidor. La clase WebSocketKeepAlive debe registrarse como TaskEntryPoint para keepAliveTrigger por la aplicación.
Algunas consideraciones especiales afectan a la forma en que se controlan las solicitudes para recibir paquetes en StreamWebSocket . En concreto, cuando se usa StreamWebSocket con ControlChannelTrigger, la aplicación debe usar un patrón asincrónico sin procesar para controlar las lecturas en lugar del modelo await en C# y VB.NET o Tasks en C++. El patrón asincrónico sin procesar se muestra en un ejemplo de código más adelante en esta sección.
Si usas el patrón asincrónico sin procesar, Windows podrá sincronizar el método IBackgroundTask.Run de la tarea en segundo plano de la clase ControlChannelTrigger mediante la devolución de la llamada de finalización recibida. Una vez devuelta la llamada de finalización, se invoca al método Run. Esto garantiza que la aplicación haya recibido los datos o errores antes de invocar el método Run .
Es importante observar que la aplicación debe publicar otra lectura antes de devolver el control de la llamada de finalización. También es importante tener en cuenta que dataReader no se puede usar directamente con el transporte MessageWebSocket o StreamWebSocket , ya que esto interrumpe la sincronización descrita anteriormente. No se admite el uso del método DataReader.LoadAsync directamente en la capa de transporte. En su lugar, el IBuffer devuelto por el método IInputStream.ReadAsync en la propiedad StreamWebSocket.InputStream se puede pasar posteriormente al método DataReader.FromBuffer para su posterior procesamiento.
En el ejemplo siguiente se muestra cómo usar un patrón asincrónico sin procesar para controlar las lecturas en StreamWebSocket.
void PostSocketRead(int length)
{
try
{
var readBuf = new Windows.Storage.Streams.Buffer((uint)length);
var readOp = socket.InputStream.ReadAsync(readBuf, (uint)length, InputStreamOptions.Partial);
readOp.Completed = (IAsyncOperationWithProgress<IBuffer, uint>
asyncAction, AsyncStatus asyncStatus) =>
{
switch (asyncStatus)
{
case AsyncStatus.Completed:
case AsyncStatus.Error:
try
{
// GetResults in AsyncStatus::Error is called as it throws a user friendly error string.
IBuffer localBuf = asyncAction.GetResults();
uint bytesRead = localBuf.Length;
readPacket = DataReader.FromBuffer(localBuf);
OnDataReadCompletion(bytesRead, readPacket);
}
catch (Exception exp)
{
Diag.DebugPrint("Read operation failed: " + exp.Message);
}
break;
case AsyncStatus.Canceled:
// Read is not cancelled in this sample.
break;
}
};
}
catch (Exception exp)
{
Diag.DebugPrint("failed to post a read failed with error: " + exp.Message);
}
}
Se garantiza que el controlador de finalización de lectura se active antes de que se invoque el método IBackgroundTask.Run en la tarea en segundo plano de ControlChannelTrigger . Recuerda que la sincronización interna de Windows debe esperar que la aplicación devuelva la llamada de finalización de lectura. Normalmente, la aplicación procesa rápidamente los datos o el error procedente de MessageWebSocket o StreamWebSocket en la función de retorno de finalización de lectura. El propio mensaje se procesa en el contexto del método IBackgroundTask.Run . En el ejemplo siguiente, este punto se ilustra mediante el uso de una cola de mensajes en la que el controlador de finalización de la lectura inserta el mensaje y que la tarea en segundo plano procesa posteriormente.
En el ejemplo siguiente se muestra el controlador de finalización de lectura que se va a usar con un patrón asincrónico sin procesar para controlar las lecturas en StreamWebSocket.
public void OnDataReadCompletion(uint bytesRead, DataReader readPacket)
{
if (readPacket == null)
{
Diag.DebugPrint("DataReader is null");
// Ideally when read completion returns error,
// apps should be resilient and try to
// recover if there is an error by posting another recv
// after creating a new transport, if required.
return;
}
uint buffLen = readPacket.UnconsumedBufferLength;
Diag.DebugPrint("bytesRead: " + bytesRead + ", unconsumedbufflength: " + buffLen);
// check if buffLen is 0 and treat that as fatal error.
if (buffLen == 0)
{
Diag.DebugPrint("Received zero bytes from the socket. Server must have closed the connection.");
Diag.DebugPrint("Try disconnecting and reconnecting to the server");
return;
}
// Perform minimal processing in the completion
string message = readPacket.ReadString(buffLen);
Diag.DebugPrint("Received Buffer : " + message);
// Enqueue the message received to a queue that the push notify
// task will pick up.
AppContext.messageQueue.Enqueue(message);
// Post another receive to ensure future push notifications.
PostSocketRead(MAX_BUFFER_LENGTH);
}
Un detalle adicional para Websockets es el controlador keep-alive. El protocolo WebSocket define un modelo estándar para los mensajes keep-alive.
Al usar MessageWebSocket o StreamWebSocket, registre una instancia de la clase WebSocketKeepAlive como TaskEntryPoint para un KeepAliveTrigger, a fin de permitir que la aplicación salga de la suspensión y pueda enviar periódicamente mensajes de mantenimiento de conexión al servidor (punto de conexión remoto). Esto debe hacerse como parte del código de registro de la aplicación en segundo plano, así como en el manifiesto del paquete.
El punto de entrada de esta tarea de Windows.Sockets.WebSocketKeepAlive debe especificarse en dos lugares:
- Al crear el desencadenador KeepAliveTrigger en el código fuente (consulte el ejemplo siguiente).
- En el manifiesto del paquete de la aplicación para la declaración de la tarea en segundo plano de conexión persistente.
En la siguiente muestra, se agrega una notificación del desencadenador de red y una notificación de la conexión persistente bajo el elemento <Application> en un manifiesto de la aplicación.
<Extensions>
<Extension Category="windows.backgroundTasks"
Executable="$targetnametoken$.exe"
EntryPoint="Background.PushNotifyTask">
<BackgroundTasks>
<Task Type="controlChannel" />
</BackgroundTasks>
</Extension>
<Extension Category="windows.backgroundTasks"
Executable="$targetnametoken$.exe"
EntryPoint="Windows.Networking.Sockets.WebSocketKeepAlive">
<BackgroundTasks>
<Task Type="controlChannel" />
</BackgroundTasks>
</Extension>
</Extensions>
Una aplicación debe tener mucho cuidado al usar una instrucción await en el contexto de un ControlChannelTrigger y una operación asincrónica en streamWebSocket, MessageWebSocket o StreamSocket. Asimismo, puedes usar un objeto Task<bool> para registrar una clase ControlChannelTrigger que corresponda con las notificaciones de inserción y las conexiones persistentes de WebSocket de la clase StreamWebSocket, y así poder conectar el transporte. Como parte del registro, el transporte StreamWebSocket se establece como transporte de la clase ControlChannelTrigger y, a continuación, se publica una lectura. Task.Result bloqueará el subproceso actual hasta que todos los pasos de la tarea ejecuten y devuelvan instrucciones en el cuerpo del mensaje. La tarea no se resuelve hasta que el método devuelve true o false. Esto garantiza que se ejecute todo el método. La tarea puede contener varias instrucciones await protegidas por la tarea. Este patrón se debe usar con el objeto ControlChannelTrigger cuando se usa StreamWebSocket o MessageWebSocket como transporte. Para esas operaciones que pueden tardar mucho tiempo en completarse (una operación típica de lectura asincrónica, por ejemplo), la aplicación debe usar el patrón asincrónico sin procesar descrito anteriormente.
La siguiente muestra registra ControlChannelTrigger para notificaciones de inserción y conexiones persistentes de WebSocket en la clase StreamWebSocket.
private bool RegisterWithControlChannelTrigger(string serverUri)
{
// Make sure the objects are created in a system thread
// Demonstrate the core registration path
// Wait for the entire operation to complete before returning from this method.
// The transport setup routine can be triggered by user control, by network state change
// or by keepalive task
Task<bool> registerTask = RegisterWithCCTHelper(serverUri);
return registerTask.Result;
}
async Task<bool> RegisterWithCCTHelper(string serverUri)
{
bool result = false;
socket = new StreamWebSocket();
// Specify the keepalive interval expected by the server for this app
// in order of minutes.
const int serverKeepAliveInterval = 30;
// Specify the channelId string to differentiate this
// channel instance from any other channel instance.
// When background task fires, the channel object is provided
// as context and the channel id can be used to adapt the behavior
// of the app as required.
const string channelId = "channelOne";
// For websockets, the system does the keepalive on behalf of the app
// But the app still needs to specify this well known keepalive task.
// This should be done here in the background registration as well
// as in the package manifest.
const string WebSocketKeepAliveTask = "Windows.Networking.Sockets.WebSocketKeepAlive";
// Try creating the controlchanneltrigger if this has not been already
// created and stored in the property bag.
ControlChannelTriggerStatus status;
// Create the ControlChannelTrigger object and request a hardware slot for this app.
// If the app is not on LockScreen, then the ControlChannelTrigger constructor will
// fail right away.
try
{
channel = new ControlChannelTrigger(channelId, serverKeepAliveInterval,
ControlChannelTriggerResourceType.RequestHardwareSlot);
}
catch (UnauthorizedAccessException exp)
{
Diag.DebugPrint("Is the app on lockscreen? " + exp.Message);
return result;
}
Uri serverUriInstance;
try
{
serverUriInstance = new Uri(serverUri);
}
catch (Exception exp)
{
Diag.DebugPrint("Error creating URI: " + exp.Message);
return result;
}
// Register the apps background task with the trigger for keepalive.
var keepAliveBuilder = new BackgroundTaskBuilder();
keepAliveBuilder.Name = "KeepaliveTaskForChannelOne";
keepAliveBuilder.TaskEntryPoint = WebSocketKeepAliveTask;
keepAliveBuilder.SetTrigger(channel.KeepAliveTrigger);
keepAliveBuilder.Register();
// Register the apps background task with the trigger for push notification task.
var pushNotifyBuilder = new BackgroundTaskBuilder();
pushNotifyBuilder.Name = "PushNotificationTaskForChannelOne";
pushNotifyBuilder.TaskEntryPoint = "Background.PushNotifyTask";
pushNotifyBuilder.SetTrigger(channel.PushNotificationTrigger);
pushNotifyBuilder.Register();
// Tie the transport method to the ControlChannelTrigger object to push enable it.
// Note that if the transport' s TCP connection is broken at a later point of time,
// the ControlChannelTrigger object can be reused to plug in a new transport by
// calling UsingTransport API again.
try
{
channel.UsingTransport(socket);
// Connect the socket
//
// If connect fails or times out it will throw exception.
// ConnectAsync can also fail if hardware slot was requested
// but none are available
await socket.ConnectAsync(serverUriInstance);
// Call WaitForPushEnabled API to make sure the TCP connection has
// been established, which will mean that the OS will have allocated
// any hardware slot for this TCP connection.
//
// In this sample, the ControlChannelTrigger object was created by
// explicitly requesting a hardware slot.
//
// On systems that without connected standby, if app requests hardware slot as above,
// the system will fallback to a software slot automatically.
//
// On systems that support connected standby,, if no hardware slot is available, then app
// can request a software slot by re-creating the ControlChannelTrigger object.
status = channel.WaitForPushEnabled();
if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
&& status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
{
throw new Exception(string.Format("Neither hardware nor software slot could be allocated. ChannelStatus is {0}", status.ToString()));
}
// Store the objects created in the property bag for later use.
CoreApplication.Properties.Remove(channel.ControlChannelTriggerId);
var appContext = new AppContext(this, socket, channel, channel.ControlChannelTriggerId);
((IDictionary<string, object>)CoreApplication.Properties).Add(channel.ControlChannelTriggerId, appContext);
result = true;
// Almost done. Post a read since we are using streamwebsocket
// to allow push notifications to be received.
PostSocketRead(MAX_BUFFER_LENGTH);
}
catch (Exception exp)
{
Diag.DebugPrint("RegisterWithCCTHelper Task failed with: " + exp.Message);
// Exceptions may be thrown for example if the application has not
// registered the background task class id for using real time communications
// broker in the package manifest.
}
return result;
}
Para obtener más información sobre el uso de MessageWebSocket o StreamWebSocket con ControlChannelTrigger, consulte el ejemplo StreamWebSocket de ControlChannelTrigger.
ControlChannelTrigger con HttpClient
Se aplican algunas consideraciones especiales al usar HttpClient con ControlChannelTrigger. Hay algunos patrones de uso específicos del transporte y procedimientos recomendados que se deben seguir al usar httpClient con ControlChannelTrigger. Además, estas consideraciones afectan a la forma en que se controlan las solicitudes para recibir paquetes en HttpClient .
NotaHttpClient que usa SSL no es actualmente compatible con la característica de activación de red ni con ControlChannelTrigger. Se deben seguir los siguientes patrones de uso y procedimientos recomendados al usar HttpClient con ControlChannelTrigger:
- Es posible que la aplicación tenga que establecer varias propiedades y encabezados en el objeto HttpClient o HttpClientHandler en el espacio de nombres System.Net.Http antes de enviar la solicitud al URI específico.
- Es posible que una aplicación necesite realizar una solicitud inicial para probar y configurar el transporte correctamente antes de crear el transporte HttpClient que se usará con ControlChannelTrigger. Una vez que la aplicación determina que el transporte puede configurarse correctamente, se puede configurar un objeto HttpClient como el objeto de transporte usado con el objeto ControlChannelTrigger . Este proceso está diseñado para evitar que algunos escenarios interrumpan la conexión establecida a través del transporte. Con SSL con un certificado, una aplicación puede requerir que se muestre un cuadro de diálogo para la entrada de PIN o si hay varios certificados entre los que elegir. Es posible que se requiera autenticación de proxy y autenticación de servidor. Si expira la autenticación de servidor o proxy, se puede cerrar la conexión. Una manera en que una aplicación puede tratar estos problemas de expiración de autenticación es establecer un temporizador. Cuando se requiere una redirección HTTP, no se garantiza que la segunda conexión se pueda establecer de forma confiable. Una solicitud de prueba inicial garantizará que la aplicación pueda usar la URL redirigida más reciente antes de usar el objeto HttpClient como transporte con el objeto ControlChannelTrigger.
A diferencia de otros transportes de red, el objeto HttpClient no se puede pasar directamente al método UsingTransport del objeto ControlChannelTrigger . En su lugar, un objeto HttpRequestMessage se debe construir especialmente para su uso con el objeto HttpClient y controlChannelTrigger. El objeto HttpRequestMessage se crea mediante el método RtcRequestFactory.Create . A continuación, el objeto HttpRequestMessage que se crea se pasa al método UsingTransport .
En el ejemplo siguiente se muestra cómo construir un objeto HttpRequestMessage para su uso con el objeto HttpClient y controlChannelTrigger.
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;
public HttpRequestMessage httpRequest;
public HttpClient httpClient;
public HttpRequestMessage httpRequest;
public ControlChannelTrigger channel;
public Uri serverUri;
private void SetupHttpRequestAndSendToHttpServer()
{
try
{
// For HTTP based transports that use the RTC broker, whenever we send next request, we will abort the earlier
// outstanding http request and start new one.
// For example in case when http server is taking longer to reply, and keep alive trigger is fired in-between
// then keep alive task will abort outstanding http request and start a new request which should be finished
// before next keep alive task is triggered.
if (httpRequest != null)
{
httpRequest.Dispose();
}
httpRequest = RtcRequestFactory.Create(HttpMethod.Get, serverUri);
SendHttpRequest();
}
catch (Exception e)
{
Diag.DebugPrint("Connect failed with: " + e.ToString());
throw;
}
}
Algunas consideraciones especiales afectan a la forma en que se procesan las solicitudes para enviar peticiones HTTP en HttpClient con el fin de iniciar la recepción de una respuesta. En concreto, cuando se usa httpClient con ControlChannelTrigger, la aplicación debe usar una tarea para controlar los envíos en lugar del modelo await .
Si se usa HttpClient, no habrá sincronización entre el método IBackgroundTask.Run de la tarea en segundo plano de ControlChannelTrigger y la devolución de llamada de finalización recibida. Por este motivo, la aplicación solo puede usar la técnica httpResponseMessage de bloqueo en el método Run y esperar hasta que se reciba toda la respuesta.
El uso de HttpClient con ControlChannelTrigger es notablemente diferente de StreamSocket, MessageWebSocket o StreamWebSocket transports . La devolución de llamada recibida de HttpClient se entrega a la aplicación mediante un elemento "Task" desde el código HttpClient. Esto significa que la tarea de notificación push ControlChannelTrigger se activará en cuanto se transmitan los datos o el error a la aplicación. En el ejemplo siguiente, el código almacena la responseTask devuelta por el método HttpClient.SendAsync en el almacenamiento global, que la tarea de notificación push recuperará y procesará en línea.
En el ejemplo siguiente se muestra cómo controlar las solicitudes de envío en HttpClient cuando se usa con ControlChannelTrigger.
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;
private void SendHttpRequest()
{
if (httpRequest == null)
{
throw new Exception("HttpRequest object is null");
}
// Tie the transport method to the controlchanneltrigger object to push enable it.
// Note that if the transport' s TCP connection is broken at a later point of time,
// the controlchanneltrigger object can be reused to plugin a new transport by
// calling UsingTransport API again.
channel.UsingTransport(httpRequest);
// Call the SendAsync function to kick start the TCP connection establishment
// process for this http request.
Task<HttpResponseMessage> httpResponseTask = httpClient.SendAsync(httpRequest);
// Call WaitForPushEnabled API to make sure the TCP connection has been established,
// which will mean that the OS will have allocated any hardware slot for this TCP connection.
ControlChannelTriggerStatus status = channel.WaitForPushEnabled();
Diag.DebugPrint("WaitForPushEnabled() completed with status: " + status);
if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
&& status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
{
throw new Exception("Hardware/Software slot not allocated");
}
// The HttpClient receive callback is delivered via a Task to the app.
// The notification task will fire as soon as the data or error is dispatched
// Enqueue the responseTask returned by httpClient.sendAsync
// into a queue that the push notify task will pick up and process inline.
AppContext.messageQueue.Enqueue(httpResponseTask);
}
En el ejemplo siguiente se muestra cómo leer las respuestas recibidas en HttpClient cuando se usa con ControlChannelTrigger.
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public string ReadResponse(Task<HttpResponseMessage> httpResponseTask)
{
string message = null;
try
{
if (httpResponseTask.IsCanceled || httpResponseTask.IsFaulted)
{
Diag.DebugPrint("Task is cancelled or has failed");
return message;
}
// We' ll wait until we got the whole response.
// This is the only supported scenario for HttpClient for ControlChannelTrigger.
HttpResponseMessage httpResponse = httpResponseTask.Result;
if (httpResponse == null || httpResponse.Content == null)
{
Diag.DebugPrint("Cannot read from httpresponse, as either httpResponse or its content is null. try to reset connection.");
}
else
{
// This is likely being processed in the context of a background task and so
// synchronously read the Content' s results inline so that the Toast can be shown.
// before we exit the Run method.
message = httpResponse.Content.ReadAsStringAsync().Result;
}
}
catch (Exception exp)
{
Diag.DebugPrint("Failed to read from httpresponse with error: " + exp.ToString());
}
return message;
}
Para obtener más información sobre el uso de HttpClient con ControlChannelTrigger, consulte el ejemplo HttpClient de ControlChannelTrigger.
ControlChannelTrigger con IXMLHttpRequest2
Se aplican algunas consideraciones especiales al usar IXMLHTTPRequest2 con ControlChannelTrigger. Hay algunos patrones de uso y procedimientos recomendados específicos del transporte que se deben seguir al usar un IXMLHTTPRequest2 con ControlChannelTrigger. El uso de ControlChannelTrigger no afecta a la forma en que se controlan las solicitudes HTTP para enviar o recibir solicitudes HTTP en IXMLHTTPRequest2 .
Patrones de uso y procedimientos recomendados al usar IXMLHTTPRequest2 con ControlChannelTrigger
- Un objeto IXMLHTTPRequest2 cuando se usa como transporte tiene una duración de solo una solicitud o respuesta. Cuando se usa con el objeto ControlChannelTrigger , es conveniente crear y configurar el objeto ControlChannelTrigger una vez y, a continuación, llamar al método UsingTransport repetidamente, cada vez que asocia un nuevo objeto IXMLHTTPRequest2 . Una aplicación debe eliminar el objeto IXMLHTTPRequest2 anterior antes de proporcionar un nuevo objeto IXMLHTTPRequest2 para asegurarse de que la aplicación no supere los límites de recursos asignados.
- Es posible que la aplicación tenga que llamar a los métodos SetProperty y SetRequestHeader para configurar el transporte HTTP antes de llamar al método Send .
- Es posible que sea necesario que una aplicación necesite una solicitud de envío inicial para probar y configurar el transporte correctamente antes de crear el transporte que se va a usar con ControlChannelTrigger. Una vez que la aplicación determina que el transporte está configurado correctamente, el objeto IXMLHTTPRequest2 se puede configurar como el objeto de transporte usado con ControlChannelTrigger. Este proceso está diseñado para evitar que algunos escenarios interrumpan la conexión establecida a través del transporte. Con SSL con un certificado, una aplicación puede requerir que se muestre un cuadro de diálogo para la entrada de PIN o si hay varios certificados entre los que elegir. Es posible que se requiera autenticación de proxy y autenticación de servidor. Si expira la autenticación de servidor o proxy, se puede cerrar la conexión. Una manera en que una aplicación puede tratar estos problemas de expiración de autenticación es establecer un temporizador. Cuando se requiere una redirección HTTP, no se garantiza que la segunda conexión se pueda establecer de forma confiable. Una solicitud de prueba inicial garantizará que la aplicación pueda usar la dirección URL redirigida más actualizada antes de usar el objeto IXMLHTTPRequest2 como transporte con el objeto ControlChannelTrigger.
Para obtener más información sobre el uso de IXMLHTTPRequest2 con ControlChannelTrigger, consulte el ejemplo ControlChannelTrigger con IXMLHTTPRequest2.