Comunicações de rede em segundo plano

Para continuar a comunicação de rede enquanto ela não estiver em primeiro plano, seu aplicativo pode usar tarefas em segundo plano e uma dessas duas opções.

  • Agente de soquete. Se o aplicativo usa soquetes para conexões de longo prazo, então, quando ele deixa o primeiro plano, ele pode delegar a propriedade de um soquete a um agente de soquete do sistema. O broker então: ativa seu aplicativo quando o tráfego chega ao soquete; transfere o controle de volta para seu aplicativo; e seu aplicativo então processa o tráfego recebido.
  • Acionadores do canal de controle.

Executando operações de rede em tarefas em segundo plano

  • Use um SocketActivityTrigger para ativar a tarefa em segundo plano quando um pacote é recebido e você precisa executar uma tarefa de curta duração. Depois de executar a tarefa, a tarefa em segundo plano deve ser encerrada para economizar energia.
  • Use um ControlChannelTrigger para ativar a tarefa em segundo plano quando um pacote é recebido e você precisa executar uma tarefa de longa duração.

Condições e sinalizadores relacionados à rede

  • Adicione a condição InternetAvailable à tarefa em segundo plano BackgroundTaskBuilder.AddCondition para atrasar o acionamento da tarefa em segundo plano até que a pilha de rede esteja em execução. Essa condição economiza energia porque a tarefa em segundo plano não será executada até que a rede esteja ativada. Essa condição não fornece ativação em tempo real.

Independentemente do gatilho usado, defina IsNetworkRequested em sua tarefa em segundo plano para garantir que a rede permaneça ativa enquanto a tarefa em segundo plano é executada. Isso informa a infraestrutura de tarefas em segundo plano para manter a rede ativa enquanto a tarefa está em execução, mesmo que o dispositivo tenha entrado no modo Connected Standby. Se sua tarefa em segundo plano não usar IsNetworkRequested, sua tarefa em segundo plano não poderá acessar a rede quando estiver no modo de Espera Conectada (por exemplo, quando a tela de um telefone estiver desativada).

Broker de Socket e SocketActivityTrigger

Se o aplicativo usar conexões DatagramSocket, StreamSocket ou StreamSocketListener , você deverá usar SocketActivityTrigger e o agente de soquete para ser notificado quando o tráfego chegar para seu aplicativo enquanto ele não estiver em primeiro plano.

Para que seu aplicativo receba e processe dados recebidos em um soquete quando seu aplicativo não estiver ativo, seu aplicativo deve executar alguma configuração única na inicialização e, em seguida, transferir a propriedade do soquete para o agente de soquete quando ele estiver fazendo a transição para um estado em que ele não esteja ativo.

As etapas de configuração inicial consistem em criar um gatilho, registrar uma tarefa em segundo plano para o gatilho e habilitar o soquete para o broker de soquete:

  • Crie um SocketActivityTrigger e registre uma tarefa em segundo plano para esse gatilho, com o parâmetro TaskEntryPoint definido para o seu código processar um pacote recebido.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Chame EnableTransferOwnership no socket antes de vincular o 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");

Depois que o soquete estiver configurado corretamente, quando o aplicativo estiver prestes a ser suspenso, chame TransferOwnership no soquete para transferi-lo para um agente de soquete. O broker monitora o socket e ativa sua tarefa em segundo plano quando recebe dados. O exemplo a seguir inclui uma função utilitária TransferOwnership para realizar a transferência de soquetes StreamSocketListener. (Observe que os diferentes tipos de soquetes têm seu próprio método TransferOwnership , portanto, você deve chamar o método apropriado para o soquete cuja propriedade você está transferindo. Seu código provavelmente conteria um auxiliar de TransferOwnership sobrecarregado com uma implementação para cada tipo de soquete usado, de modo que o código OnSuspending permaneça fácil de ler.)

Um aplicativo transfere o controle de um soquete para um intermediário de soquete e passa a ID da tarefa em segundo plano usando o método apropriado dentre os seguintes:


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

No manipulador de eventos da sua tarefa em segundo plano:

  • Primeiro, obtenha um adiamento da tarefa em segundo plano para que você possa lidar com o evento usando métodos assíncronos.
var deferral = taskInstance.GetDeferral();
  • Em seguida, extraia SocketActivityTriggerDetails dos argumentos do evento e localize o motivo pelo qual o evento foi gerado:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Se o evento foi gerado devido à atividade de soquete, crie um DataReader no soquete, carregue o leitor de forma assíncrona e use os dados de acordo com o design do aplicativo. Observe que você deve devolver a propriedade do soquete ao broker de soquete para ser notificado novamente sobre outras atividades do soquete.

No exemplo a seguir, o texto recebido no socket é exibido em uma notificação toast.

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;
  • Se o evento foi gerado porque um temporizador keep alive expirou, o código deverá enviar alguns dados pelo soquete para manter o soquete ativo e reiniciar o temporizador keep alive. Mais uma vez, é importante devolver o controle do soquete ao gerenciador de soquetes para receber mais notificações 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;
  • Se o evento foi gerado porque o soquete foi fechado, restabeleça o soquete, certificando-se de que, depois de criar o novo soquete, você transfira a propriedade dele para o agente de soquete. Neste exemplo, o nome do host e a porta são armazenados nas configurações locais para que possam ser usados para estabelecer uma nova conexão de soquete:
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;
  • Não se esqueça de concluir o adiamento depois de concluir o processamento da notificação de evento:
  deferral.Complete();

Para obter um exemplo completo que demonstra o uso do SocketActivityTrigger e do agente de soquete, consulte o exemplo SocketActivityStreamSocket. A inicialização do soquete é executada em Scenario1_Connect.xaml.cs e a implementação da tarefa em segundo plano está em SocketActivityTask.cs.

Você provavelmente observará que o exemplo chama TransferOwnership assim que ele cria um novo soquete ou adquire um soquete existente, em vez de usar o manipulador OnSuspending even para fazer isso, conforme descrito neste tópico. Isso ocorre porque o exemplo se concentra em demonstrar o SocketActivityTrigger e não usa o soquete para nenhuma outra atividade enquanto ele está em execução. Seu aplicativo provavelmente será mais complexo e deverá usar o OnSuspending para determinar quando chamar TransferOwnership.

Disparadores do canal de controle

Primeiro, certifique-se de que você está usando os CCTs (gatilhos de canal de controle) corretamente. Se você estiver usando conexões DatagramSocket, StreamSocket ou StreamSocketListener , recomendamos que você use SocketActivityTrigger. Você pode usar CCTs para StreamSocket, mas eles usam mais recursos e podem não funcionar no modo de Espera Conectada.

Se você estiver usando WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient ou Windows. Web.Http.HttpClient, você deve usar ControlChannelTrigger.

ControlChannelTrigger com WebSockets

Important

O recurso descrito nesta seção (ControlChannelTrigger com WebSockets) tem suporte no Windows SDK versão 10.0.15063.0 e posterior.

Algumas considerações especiais se aplicam ao usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger. Há alguns padrões de uso específicos de transporte e práticas recomendadas que devem ser seguidas ao usar um MessageWebSocket ou StreamWebSocket com ControlChannelTrigger. Além disso, essas considerações afetam a maneira como as solicitações para receber pacotes no StreamWebSocket são tratadas. As solicitações para receber pacotes no MessageWebSocket não são afetadas.

Os seguintes padrões de uso e práticas recomendadas devem ser seguidos ao usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger:

  • Uma operação de recebimento pendente no socket deve ser mantida sempre registrada. Isso é necessário para permitir que as tarefas de notificação por push ocorram.
  • O protocolo WebSocket define um modelo padrão para mensagens keep-alive. A classe WebSocketKeepAlive pode enviar mensagens de manutenção do protocolo WebSocket iniciadas pelo cliente para o servidor. A classe WebSocketKeepAlive deve ser registrada como TaskEntryPoint para um KeepAliveTrigger pelo aplicativo.

Algumas considerações especiais afetam a maneira como as solicitações para receber pacotes no StreamWebSocket são tratadas. Em particular, ao usar um StreamWebSocket com o ControlChannelTrigger, seu aplicativo deve usar um padrão assíncrono bruto para lidar com leituras em vez do modelo de espera em C# e VB.NET ou Tarefas em C++. O padrão assíncrono bruto é ilustrado em um exemplo de código posteriormente nesta seção.

Usar o padrão assíncrono bruto permite que o Windows sincronize o método IBackgroundTask.Run da tarefa em segundo plano do ControlChannelTrigger com o retorno da chamada de retorno de conclusão de recebimento. O método Run é invocado depois que o callback de conclusão retorna. Isso garante que o aplicativo tenha recebido os dados/erros antes que o método Executar seja invocado.

É importante observar que o aplicativo precisa emitir outra solicitação de leitura antes de devolver o controle no callback de conclusão. Também é importante observar que o DataReader não pode ser usado diretamente com o transporte MessageWebSocket ou StreamWebSocket , pois isso interrompe a sincronização descrita acima. Não há suporte para o uso do método DataReader.LoadAsync diretamente no transporte. Em vez disso, o IBuffer retornado pelo método IInputStream.ReadAsync na propriedade StreamWebSocket.InputStream pode ser passado posteriormente para o método DataReader.FromBuffer para processamento adicional.

O exemplo a seguir mostra como usar um padrão assíncrono bruto para lidar com leituras no 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);
   }
}

É garantido que o manipulador de conclusão da leitura seja acionado antes que o método IBackgroundTask.Run da tarefa em segundo plano para o ControlChannelTrigger seja invocado. O Windows conta com sincronização interna para aguardar que um aplicativo retorne do callback de conclusão de leitura. O aplicativo normalmente processa rapidamente os dados ou o erro provenientes de MessageWebSocket ou de StreamWebSocket no retorno de chamada de conclusão da leitura. A mensagem em si é processada no contexto do método IBackgroundTask.Run . Neste exemplo abaixo, esse ponto é ilustrado usando uma fila de mensagens na qual o manipulador de conclusão de leitura insere a mensagem e a tarefa em segundo plano é processada posteriormente.

O exemplo a seguir mostra o manipulador de conclusão de leitura a ser usado com um padrão assíncrono bruto para lidar com leituras no 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);
}

Um detalhe adicional para WebSockets é o mecanismo de keep-alive. O protocolo WebSocket define um modelo padrão para mensagens keep-alive.

Ao usar MessageWebSocket ou StreamWebSocket, registre uma instância da classe WebSocketKeepAlive como TaskEntryPoint de um KeepAliveTrigger para permitir que o aplicativo seja retirado do estado suspenso e envie mensagens de keep-alive para o servidor (extremidade remota) periodicamente. Isso deve ser feito no código do aplicativo de registro em segundo plano, bem como no manifesto do pacote.

Este ponto de entrada de tarefa de Windows. Sockets.WebSocketKeepAlive precisa ser especificado em dois lugares:

  • Ao criar o gatilho KeepAliveTrigger no código-fonte (veja o exemplo abaixo).
  • No arquivo de manifesto do pacote do aplicativo para a declaração da tarefa em segundo plano keepalive.

O exemplo a seguir adiciona uma notificação de gatilho de rede e um gatilho keepalive sob o <elemento Application> em um manifesto do aplicativo.

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

Um aplicativo deve ser extremamente cuidadoso ao usar uma instrução await no contexto de um ControlChannelTrigger e uma operação assíncrona em um StreamWebSocket, MessageWebSocket ou StreamSocket. Um objeto Task<bool> pode ser usado para registrar um ControlChannelTrigger para notificações por push e mensagens de keep-alive de WebSocket no StreamWebSocket e conectar o transporte. Como parte do registro, o transporte StreamWebSocket é configurado como transporte de ControlChannelTrigger e uma operação de leitura é iniciada. O Task.Result bloqueará o thread atual até que todas as etapas na tarefa executem e retornem instruções no corpo da mensagem. A tarefa não será resolvida até que o método retorne true ou false. Isso garante que todo o método seja executado. A Tarefa pode conter várias instruções de espera protegidas pela Tarefa. Esse padrão deve ser usado com o objeto ControlChannelTrigger quando um StreamWebSocket ou MessageWebSocket é usado como o transporte. Para essas operações que podem levar um longo período de tempo para serem concluídas (uma operação de leitura assíncrona típica, por exemplo), o aplicativo deve usar o padrão assíncrono bruto discutido anteriormente.

O exemplo a seguir registra ControlChannelTrigger para notificação por push e keep-alives do WebSocket no 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 obter mais informações sobre como usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger, consulte o exemplo controlChannelTrigger StreamWebSocket.

ControlChannelTrigger com HttpClient

Algumas considerações especiais se aplicam ao usar HttpClient com ControlChannelTrigger. Há alguns padrões de uso específicos de transporte e práticas recomendadas que devem ser seguidas ao usar um HttpClient com ControlChannelTrigger. Além disso, essas considerações afetam a maneira como as solicitações para receber pacotes no HttpClient são tratadas.

ObservaçãoHttpClient usando SSL não tem suporte no momento ao usar o recurso de acionador de rede e ControlChannelTrigger.   Os seguintes padrões de uso e práticas recomendadas devem ser seguidos ao usar HttpClient com ControlChannelTrigger:

  • O aplicativo pode precisar definir várias propriedades e cabeçalhos no objeto HttpClient ou HttpClientHandler no namespace System.Net.Http antes de enviar a solicitação para o URI específico.
  • Um aplicativo pode precisar fazer uma solicitação inicial para testar e configurar o transporte corretamente antes de criar o transporte HttpClient para ser usado com ControlChannelTrigger. Depois que o aplicativo determina que o transporte pode ser configurado corretamente, um objeto HttpClient pode ser configurado como o objeto de transporte usado com o objeto ControlChannelTrigger . Esse processo foi projetado para impedir que alguns cenários interrompam a conexão estabelecida por meio do transporte. Usando o SSL com um certificado, um aplicativo pode exigir que uma caixa de diálogo seja exibida para a entrada de PIN ou se houver vários certificados para escolher. A autenticação de proxy e a autenticação do servidor podem ser necessárias. Se a autenticação de servidor ou proxy expirar, a conexão poderá ser fechada. Uma maneira de um aplicativo lidar com esses problemas de expiração de autenticação é definir um temporizador. Quando um redirecionamento HTTP é necessário, não é garantido que a segunda conexão possa ser estabelecida de forma confiável. Uma solicitação de teste inicial garantirá que o aplicativo possa usar a URL redirecionada mais atualizada antes de usar o objeto HttpClient como transporte com o objeto ControlChannelTrigger.

Ao contrário de outros transportes de rede, o objeto HttpClient não pode ser transmitido diretamente para o método UsingTransport do objeto ControlChannelTrigger . Em vez disso, um objeto HttpRequestMessage deve ser construído especialmente para uso com o objeto HttpClient e o ControlChannelTrigger. O objeto HttpRequestMessage é criado usando o método RtcRequestFactory.Create . O objeto HttpRequestMessage que é criado é então passado para o método UsingTransport .

O exemplo a seguir mostra como construir um objeto HttpRequestMessage para uso com o objeto HttpClient e o 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;
    }
}

Algumas considerações especiais afetam a maneira como as solicitações para enviar solicitações HTTP no HttpClient para iniciar o recebimento de uma resposta são tratadas. Em particular, ao usar um HttpClient com o ControlChannelTrigger, seu aplicativo deve usar uma Tarefa para lidar com envios em vez do modelo de espera .

Usando HttpClient, não há sincronização entre o método IBackgroundTask.Run da tarefa em segundo plano para o ControlChannelTrigger e o retorno do callback de conclusão de recebimento. Por esse motivo, o aplicativo só pode usar a técnica HttpResponseMessage de bloqueio no método Run e aguardar até que toda a resposta seja recebida.

Usar HttpClient com ControlChannelTrigger é notavelmente diferente dos transportes StreamSocket, MessageWebSocket ou StreamWebSocket. O retorno de chamada de recebimento httpClient é entregue por meio de uma Tarefa para o aplicativo desde o código HttpClient . Isso significa que a tarefa de notificação por push ControlChannelTrigger será acionada assim que os dados ou o erro forem enviados para o aplicativo. No exemplo abaixo, o código armazena a responseTask retornada pelo método HttpClient.SendAsync no armazenamento global, que a tarefa de notificação por push recuperará e processará em linha.

O exemplo a seguir mostra como lidar com solicitações de envio no HttpClient quando usado com 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);
}

O exemplo a seguir mostra como ler as respostas recebidas no HttpClient quando usadas com 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 obter mais informações sobre como usar HttpClient com ControlChannelTrigger, consulte o exemplo httpClient controlChannelTrigger.

ControlChannelTrigger com IXMLHttpRequest2

Algumas considerações especiais se aplicam ao usar IXMLHTTPRequest2 com ControlChannelTrigger. Há alguns padrões de uso específicos de transporte e práticas recomendadas que devem ser seguidas ao usar um IXMLHTTPRequest2 com ControlChannelTrigger. O uso de ControlChannelTrigger não afeta a maneira como as solicitações para enviar ou receber solicitações HTTP no IXMLHTTPRequest2 são tratadas.

Padrões de uso e práticas recomendadas ao usar IXMLHTTPRequest2 com ControlChannelTrigger

  • Um objeto IXMLHTTPRequest2 quando usado como transporte tem um tempo de vida de apenas uma solicitação/resposta. Quando usado com o objeto ControlChannelTrigger , é conveniente criar e configurar o objeto ControlChannelTrigger uma vez e, em seguida, chamar o método UsingTransport repetidamente, cada vez associando um novo objeto IXMLHTTPRequest2 . Um aplicativo deve excluir o objeto IXMLHTTPRequest2 anterior antes de fornecer um novo objeto IXMLHTTPRequest2 para garantir que o aplicativo não exceda os limites de recursos alocados.
  • O aplicativo pode precisar chamar os métodos SetProperty e SetRequestHeader para configurar o transporte HTTP antes de chamar o método Send .
  • Um aplicativo pode precisar fazer uma solicitação de envio inicial para testar e configurar o transporte corretamente antes de criar o transporte a ser usado com ControlChannelTrigger. Depois que o aplicativo determina que o transporte está configurado corretamente, o objeto IXMLHTTPRequest2 pode ser configurado como o objeto de transporte usado com o ControlChannelTrigger. Esse processo foi projetado para impedir que alguns cenários interrompam a conexão estabelecida pelo transporte. Usando o SSL com um certificado, um aplicativo pode exigir que uma caixa de diálogo seja exibida para a entrada de PIN ou se houver vários certificados para escolher. A autenticação de proxy e a autenticação do servidor podem ser necessárias. Se a autenticação de servidor ou proxy expirar, a conexão poderá ser fechada. Uma maneira de um aplicativo lidar com esses problemas de expiração de autenticação é definir um temporizador. Quando um redirecionamento HTTP é necessário, não é garantido que a segunda conexão possa ser estabelecida de forma confiável. Uma solicitação de teste inicial garantirá que o aplicativo possa usar a URL redirecionada mais atualizada antes de usar o objeto IXMLHTTPRequest2 como transporte com o objeto ControlChannelTrigger.

Para obter mais informações sobre como usar IXMLHTTPRequest2 com ControlChannelTrigger, consulte o ControlChannelTrigger com o exemplo IXMLHTTPRequest2.

APIs importantes