Partilhar via


Comunicações de rede em segundo plano

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

  • Intermediário de soquetes Se seu aplicativo usa soquetes para conexões de longo prazo, então, quando ele sai do primeiro plano, ele pode delegar a propriedade de um soquete a um agente de soquete do sistema. O broker então: ativa a sua aplicação quando o tráfego chega ao socket; transfere a propriedade de volta para a sua aplicação; e a sua aplicação então processa o tráfego que chega.
  • Acionadores do canal de controlo.

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

  • Use um SocketActivityTrigger para ativar a tarefa em segundo plano quando um pacote for recebido e for necessário 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 for recebido e for necessário executar uma tarefa de longa duração.

Condições e sinalizadores relacionados com a rede

  • Adicione a condição de InternetAvailable à sua tarefa em segundo plano BackgroundTaskBuilder.AddCondition 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. Esta 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. Isto instrui a infraestrutura de tarefa em segundo plano a manter a rede ativa enquanto a tarefa é executada, mesmo que o dispositivo entre no modo de espera conectado. Se a sua tarefa em segundo plano não usar IsNetworkRequested, então a sua tarefa em segundo plano não poderá acessar a rede quando estiver no modo de espera ligado (por exemplo, quando a tela de um telefone estiver desligada).

Intermediário de soquete e o Activador de Actividade de Soquete

Se seu aplicativo usa DatagramSocket , StreamSocketou conexões de StreamSocketListener, você deve 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 não esteja ativo.

As etapas de configuração única são criar um gatilho, registrar uma tarefa em segundo plano para o gatilho e habilitar o soquete para o agente de soquete:

  • Crie um SocketActivityTrigger e registe uma tarefa em segundo plano para o gatilho, com o parâmetro TaskEntryPoint definido para o seu código de processamento de 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 soquete, antes de vincular o soquete.
           _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");

Quando o soquete estiver configurado corretamente e a sua aplicação estiver prestes a ser suspensa, chame TransferOwnership no soquete para transferi-lo para um broker de soquete. O broker monitora o soquete e ativa sua tarefa em segundo plano quando os dados são recebidos. O exemplo a seguir inclui uma função de utilitário de transferência de propriedade para executar a transferência para soquetes de StreamSocketListener. (Observe que os diferentes tipos de soquetes têm cada um 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 sobrecarregado TransferOwnership auxiliar com uma implementação para cada tipo de soquete que você usa, para que o código OnSuspending permaneça fácil de ler.)

Uma aplicação transfere a propriedade de um socket para um broker de soquetes e envia o ID da tarefa em segundo plano utilizando um dos métodos apropriados a seguir:


// declare int _transferOwnershipCount as a field.

private 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 tarefa em segundo plano:

  • Primeiro, obtenha um adiamento de tarefa em segundo plano para que você possa manipular o evento usando métodos assíncronos.
var deferral = taskInstance.GetDeferral();
  • Em seguida, extraia o SocketActivityTriggerDetails dos argumentos de 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 do soquete, crie um DataReader no soquete, carregue o leitor de forma assíncrona e use os dados de acordo com o design do seu aplicativo. Observe que você deve retornar a propriedade do soquete de volta para o agente de soquete, a fim de ser notificado de atividade adicional do soquete novamente.

No exemplo a seguir, o texto recebido no soquete é exibido em um brinde.

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, seu código deve enviar alguns dados pelo soquete para manter o soquete ativo e reiniciar o temporizador keep alive. Novamente, é importante devolver a propriedade do soquete ao agente de soquetes para receber novas 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 em 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, assim que terminar de processar a notificação do evento:
  deferral.Complete();

Para obter um exemplo completo demonstrando o uso do SocketActivityTrigger e do transmissor de soquetes, 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 notará que o exemplo chama TransferOwnership assim que cria um novo soquete ou adquire um soquete existente, em vez de usar o OnSuspending mesmo manipulador para fazê-lo, conforme descrito neste tópico. Isso ocorre porque o exemplo se concentra em demonstrar o SocketActivityTriggere não usa o soquete para nenhuma outra atividade enquanto ele está em execução. Seu aplicativo provavelmente será mais complexo e deve usar OnSuspending para determinar quando chamar TransferOwnership.

Gatilhos do canal de controle

Primeiro, certifique-se de que você está usando gatilhos de canal de controle (CCTs) adequadamente. Se estiveres a utilizar DatagramSocket, StreamSocketou StreamSocketListener, recomendamos que uses SocketActivityTrigger. Você pode usar CCTs para StreamSocket, mas elas usam mais recursos e podem não funcionar no modo de espera conectado.

Se você estiver usando WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClientou Windows.Web.Http.HttpClient, então você deve usar ControlChannelTrigger.

ControlChannelTrigger com WebSockets

Importante

O recurso descrito nesta seção (ControlChannelTrigger com WebSockets) é suportado na versão 10.0.15063.0 do SDK e anteriores. Também é suportado em versões de pré-lançamento do Windows 10 Insider Preview.

Algumas considerações especiais se aplicam ao usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger. Há alguns padrões de uso específicos do 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 :

  • Um recebimento de socket pendente deve ser mantido informado em todos os momentos. 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 para o servidor mensagens do protocolo WebSocket keep-alive iniciadas pelo cliente. A classe WebSocketKeepAlive deve ser registada como TaskEntryPoint para um KeepAliveTrigger pela aplicação.

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

O uso do padrão assíncrono bruto permite que o Windows sincronize o método IBackgroundTask.Run na tarefa em segundo plano para o ControlChannelTrigger com o retorno do retorno de chamada de conclusão de recebimento. O método Run é invocado após a conclusão do retorno de chamada. Isso garante que o aplicativo tenha recebido os dados/erros antes que o método Run seja invocado.

É importante notar que a aplicação tem de fazer outra leitura antes de devolver o controle do callback de conclusão. Também é importante observar que o DataReader não pode ser usado diretamente com o transporte de MessageWebSocket ou StreamWebSocket, pois isso interrompe a sincronização descrita acima. Não é suportado usar o método DataReader.LoadAsync diretamente sobre o 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 um processamento adicional.

O exemplo a seguir mostra como usar um padrão assíncrono bruto para manipular 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 de leitura seja acionado antes que o método IBackgroundTask.Run na tarefa em segundo plano para o ControlChannelTrigger seja invocado. O Windows tem sincronização interna para aguardar que um aplicativo conclua o retorno de chamada de leitura. A app normalmente processa rapidamente os dados ou o erro do MessageWebSocket ou StreamWebSocket no callback de conclusão de leitura. A mensagem em si é processada dentro do 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 processa posteriormente.

O exemplo a seguir mostra o manipulador de conclusão de leitura a ser usado com um padrão assíncrono bruto para manipular 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 manipulador keep-alive. O protocolo WebSocket define um modelo padrão para mensagens keep-alive.

Ao utilizar MessageWebSocket ou StreamWebSocket, registe uma instância da classe WebSocketKeepAlive como o TaskEntryPoint para um KeepAliveTrigger, permitindo que a aplicação não seja suspensa e envie periodicamente mensagens de keep-alive para o servidor (ponto de extremidade remoto). Isso deve ser feito como parte do código do aplicativo de registo em segundo plano, assim como no manifesto do pacote.

Esse ponto de entrada de tarefa de Windows.Sockets.WebSocketKeepAlive precisa ser especificado em dois locais:

  • Ao criar o gatilho KeepAliveTrigger no código-fonte (veja o exemplo abaixo).
  • No manifesto do pacote do aplicativo para a declaração de 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 de 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>

Uma aplicação deve ser extremamente cuidadosa ao usar uma instrução await no contexto de um ControlChannelTrigger e uma operação assíncrona num StreamWebSocket, MessageWebSocket, ou StreamSocket. Um objeto Task<bool> pode ser usado para registar um ControlChannelTrigger para notificação por push e WebSocket keep-alives no StreamWebSocket e conectar o transporte. Como parte do registro, o transporte de StreamWebSocket é definido como o transporte para o ControlChannelTrigger e uma leitura é postada. O Task.Result bloqueará a sequência atual até que todas as etapas da tarefa sejam executadas e retornem resultados no corpo da mensagem. A tarefa não é 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 await que são protegidas pela Tarefa. Este padrão deve ser usado com o objeto ControlChannelTrigger quando um StreamWebSocket ou um MessageWebSocket for usado como meio de transporte. Para as 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 regista ControlChannelTrigger para notificações por push e manutenção de conexão 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 mais informações sobre como usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger, consulte o exemplo ControlChannelTrigger StreamWebSocket.

ControlChannelTrigger com HttpClient

Algumas considerações especiais aplicam-se quando se utiliza HttpClient com ControlChannelTrigger. Existem alguns padrões de uso específicos para 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 é suportado atualmente usando o recurso de gatilho de rede e ControlChannelTrigger.   Os seguintes padrões de uso e práticas recomendadas devem ser seguidos ao usar HttpClient com ControlChannelTrigger :

  • A aplicação pode precisar definir várias propriedades e cabeçalhos no objeto HttpClient ou HttpClientHandler dentro do 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 HttpClient transporte a ser usado com ControlChannelTrigger. Uma vez que o aplicativo determina que o transporte pode ser configurado corretamente, um objeto HttpClient pode ser definido como o objeto de transporte usado com o objeto ControlChannelTrigger. Este processo é projetado para evitar que alguns cenários quebrem a conexão estabelecida sobre o transporte. Usando SSL com um certificado, um aplicativo pode exigir que uma caixa de diálogo seja exibida para a entrada do PIN ou se houver vários certificados para escolher. A autenticação de proxy e a autenticação de servidor podem ser necessárias. Se a autenticação de proxy ou servidor 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 up-toantes 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 passado diretamente para o método UsingTransport do objeto ControlChannelTrigger . Em vez disso, um objeto HttpRequestMessage deve ser especialmente construído para uso com o objeto HttpClient e o ControlChannelTrigger. O objeto HttpRequestMessage é criado usando o RtcRequestFactory.Create método. O objeto HttpRequestMessage que foi 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. Particularmente, ao usar um HttpClient com o ControlChannelTrigger, a sua aplicação deve usar uma tarefa para manipular envios em vez do modelo aguardar.

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

Usar HttpClient com ControlChannelTrigger é visivelmente diferente do StreamSocket , do MessageWebSocket , ou do StreamWebSocket transporte. O HttpClient receber retorno de chamada é entregue através de uma tarefa para o aplicativo desde o HttpClient código. Isso significa que a tarefa de notificação por push do ControlChannelTrigger será ativada assim que os dados ou o erro forem enviados à aplicação. No exemplo abaixo, o código armazena o responseTask retornado pelo método HttpClient.SendAsync no armazenamento global que a tarefa de notificação por push receberá 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 respostas recebidas no HttpClient quando usado 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 utilizar o HttpClient com ControlChannelTrigger , consulte o exemplo do ControlChannelTrigger HttpClient .

ControlChannelTrigger com IXMLHttpRequest2

Algumas considerações especiais se aplicam ao usar IXMLHTTPRequest2 com ControlChannelTrigger. Existem alguns padrões de uso específicos de transporte e práticas recomendadas que devem ser seguidos ao usar um IXMLHTTPRequest2 com ControlChannelTrigger. O uso do ControlChannelTrigger não afeta a maneira como as solicitações para enviar ou receber pedidos 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 de 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 inicial enviar para testar e configurar o transporte corretamente antes de criar o transporte a ser usado com ControlChannelTrigger. Assim que o aplicativo determinar que o transporte está configurado corretamente, o objeto IXMLHTTPRequest2 pode ser configurado como o objeto de transporte usado com o ControlChannelTrigger. Este processo é projetado para evitar que alguns cenários quebrem a conexão estabelecida sobre o transporte. Usando SSL com um certificado, um aplicativo pode exigir que uma caixa de diálogo seja exibida para a entrada do PIN ou se houver vários certificados para escolher. A autenticação de proxy e a autenticação de servidor podem ser necessárias. Se a autenticação de proxy ou servidor 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 atual up-toantes de usar o objeto IXMLHTTPRequest2 como o transporte com o objeto ControlChannelTrigger.

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

APIs importantes

  • GatilhoDeAtividadeDeSocket
  • ControlChannelTrigger