Communications réseau en arrière-plan

Pour continuer la communication réseau alors qu’elle n’est pas au premier plan, votre application peut utiliser des tâches en arrière-plan et l’une de ces deux options.

  • Répartiteur de sockets. Si votre application utilise des sockets pour les connexions à long terme, lorsqu’elle quitte le premier plan, elle peut déléguer la propriété d’un socket à un répartiteur de sockets système. Le répartiteur active ensuite votre application lorsque le trafic arrive sur le socket ; transfère la propriété à votre application ; et votre application traite ensuite le trafic arrivant.
  • Déclencheurs de canal de contrôle.

Exécution d’opérations réseau dans des tâches en arrière-plan

  • Utilisez un SocketActivityTrigger pour activer la tâche en arrière-plan lorsqu’un paquet est reçu et que vous devez effectuer une tâche de courte durée. Après avoir effectué la tâche, la tâche en arrière-plan doit s’arrêter pour économiser de l’énergie.
  • Utilisez un ControlChannelTrigger pour activer la tâche en arrière-plan lorsqu’un paquet est reçu et que vous devez effectuer une tâche de longue durée.

Conditions et indicateurs liés au réseau

  • Ajoutez la condition InternetAvailable à votre tâche en arrière-plan BackgroundTaskBuilder.AddCondition pour retarder le déclenchement de la tâche en arrière-plan jusqu’à l’exécution de la pile réseau. Cette condition économise de l’énergie, car la tâche d’arrière-plan ne s’exécutera pas tant que le réseau n’est pas disponible. Cette condition ne fournit pas d’activation en temps réel.

Quel que soit le déclencheur que vous utilisez, définissez IsNetworkRequested sur votre tâche d'arrière-plan pour vous assurer que le réseau reste opérationnel lorsque la tâche s'exécute. Cela indique à l’infrastructure de tâche en arrière-plan de maintenir le réseau pendant l’exécution de la tâche, même si l’appareil a entré en mode veille connectée. Si votre tâche en arrière-plan n’utilise pas IsNetworkRequested, votre tâche en arrière-plan ne pourra pas accéder au réseau en mode Veille connectée (par exemple, lorsque l’écran d’un téléphone est désactivé).

Courtier de sockets et SocketActivityTrigger

Si votre application utilise Des connexions DatagramSocket, StreamSocket ou StreamSocketListener , vous devez utiliser SocketActivityTrigger et le répartiteur de sockets pour être averti lorsque le trafic arrive pour votre application alors qu'elle n'est pas au premier plan.

Pour que votre application reçoive et traite les données reçues sur un socket lorsque votre application n’est pas active, votre application doit effectuer une configuration ponctuelle au démarrage, puis transférer la propriété du socket au répartiteur de sockets lorsqu’elle passe à un état où elle n’est pas active.

Les étapes de configuration ponctuelles sont de créer un déclencheur, d’inscrire une tâche en arrière-plan pour le déclencheur et d’activer le socket pour le répartiteur de sockets :

  • Créez un SocketActivityTrigger et inscrivez une tâche en arrière-plan pour le déclencheur avec le paramètre TaskEntryPoint défini sur votre code pour le traitement d’un paquet reçu.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Appelez EnableTransferOwnership sur le socket, avant de lier le 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");

Une fois que votre socket est correctement configuré, lorsque votre application est sur le point de s’interrompre, appelez TransferOwnership sur le socket pour le transférer vers un répartiteur de sockets. Le broker surveille le socket et active votre tâche de fond lorsque des données sont reçues. L’exemple suivant inclut une fonction TransferOwnership utilitaire pour effectuer le transfert pour les sockets StreamSocketListener . (Notez que les différents types de sockets ont chacun leur propre méthode TransferOwnership . Vous devez donc appeler la méthode appropriée pour le socket dont vous transférez la propriété. Votre code contient probablement un helper TransferOwnership surchargé avec une implémentation pour chaque type de socket que vous utilisez, afin que le code OnSuspending reste facile à lire.)

Une application transfère la propriété d’un socket à un répartiteur de sockets et transmet l’ID de la tâche en arrière-plan à l’aide de l’une des méthodes suivantes :


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

Dans le gestionnaire d’événements de votre tâche en arrière-plan :

  • Tout d’abord, obtenez un report de tâche en arrière-plan pour pouvoir gérer l’événement à l’aide de méthodes asynchrones.
var deferral = taskInstance.GetDeferral();
  • Ensuite, extrayez socketActivityTriggerDetails des arguments d’événement et recherchez la raison pour laquelle l’événement a été déclenché :
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Si l’événement a été déclenché en raison de l’activité de socket, créez un DataReader sur le socket, chargez le lecteur de manière asynchrone, puis utilisez les données en fonction de la conception de votre application. Notez que vous devez restituer la propriété du socket au gestionnaire de sockets afin d’être à nouveau informé de toute activité ultérieure sur le socket.

Dans l’exemple suivant, le texte reçu sur le socket s’affiche dans un 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;
  • Si l’événement a été déclenché parce qu’un minuteur de conservation en vie a expiré, votre code doit envoyer des données sur le socket afin de maintenir le socket actif et redémarrer le minuteur de maintien en vie. Là encore, il est important de renvoyer la propriété du socket au répartiteur de sockets afin de recevoir d’autres notifications d’événements :
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 l’événement a été déclenché, car le socket a été fermé, rétablissez le socket, en vous assurant qu’après avoir créé le nouveau socket, vous transférez la propriété de celui-ci au répartiteur de sockets. Dans cet exemple, le nom d’hôte et le port sont stockés dans les paramètres locaux afin qu’ils puissent être utilisés pour établir une nouvelle connexion 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;
  • N’oubliez pas de terminer votre report, une fois que vous avez terminé de traiter la notification d’événement :
  deferral.Complete();

Pour obtenir un exemple complet illustrant l’utilisation du socketActivityTrigger et du répartiteur de sockets, consultez l’exemple SocketActivityStreamSocket. L’initialisation du socket est effectuée dans Scenario1_Connect.xaml.cs, et l’implémentation de tâche en arrière-plan se trouve dans SocketActivityTask.cs.

Vous remarquerez probablement que l’exemple appelle TransferOwnership dès qu’il crée un socket ou acquiert un socket existant, plutôt que d’utiliser le gestionnaire OnSuspending même pour le faire, comme décrit dans cette rubrique. Cela est dû au fait que l'exemple se concentre sur la démonstration du SocketActivityTrigger et n'utilise pas le socket pour une autre activité pendant son exécution. Votre application sera probablement plus complexe et doit utiliser OnSuspending pour déterminer quand appeler TransferOwnership.

Déclencheurs du canal de commande

Tout d’abord, vérifiez que vous utilisez correctement des déclencheurs de canal de contrôle (CCT). Si vous utilisez des connexions DatagramSocket, StreamSocket ou StreamSocketListener , nous vous recommandons d'utiliser SocketActivityTrigger. Vous pouvez utiliser des ccT pour StreamSocket, mais ils utilisent davantage de ressources et peuvent ne pas fonctionner en mode Veille connectée.

Si vous utilisez WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient ou Windows. Web.Http.HttpClient, vous devez utiliser ControlChannelTrigger.

ControlChannelTrigger avec WebSockets

Important

La fonctionnalité décrite dans cette section (ControlChannelTrigger avec WebSockets) est prise en charge dans Windows SDK version 10.0.15063.0 et versions ultérieures.

Certaines considérations spéciales s’appliquent lors de l’utilisation de MessageWebSocket ou StreamWebSocket avec ControlChannelTrigger. Il existe des modèles d’utilisation spécifiques au transport et des bonnes pratiques qui doivent être suivies lors de l’utilisation d’un MessageWebSocket ou StreamWebSocket avec ControlChannelTrigger. En outre, ces considérations affectent la façon dont les demandes de réception de paquets sur StreamWebSocket sont gérées. Les demandes de réception de paquets sur MessageWebSocket ne sont pas affectées.

Les modèles d’utilisation et les bonnes pratiques suivants doivent être suivis lors de l’utilisation de MessageWebSocket ou StreamWebSocket avec ControlChannelTrigger :

  • Une réception de socket en attente doit être conservée à tout moment. Cela est nécessaire pour autoriser les tâches de notification Push à se produire.
  • Le protocole WebSocket définit un modèle standard pour les messages keep-alive. La classe WebSocketKeepAlive peut envoyer des messages webSocket initiés par le client au serveur. La classe WebSocketKeepAlive doit être inscrite en tant que TaskEntryPoint pour un KeepAliveTrigger par l’application.

Certaines considérations spéciales affectent la façon dont les demandes de réception de paquets sur StreamWebSocket sont gérées. En particulier, lors de l’utilisation d’un StreamWebSocket avec ControlChannelTrigger, votre application doit utiliser un modèle asynchrone brut pour gérer les lectures au lieu du modèle await en C# et VB.NET ou Tâches en C++. Le modèle asynchrone brut est illustré dans un exemple de code plus loin dans cette section.

L’utilisation du modèle asynchrone brut permet Windows de synchroniser la méthode IBackgroundTask.Run sur la tâche en arrière-plan pour ControlChannelTrigger avec le retour du rappel de fin de réception. La méthode Run est appelée après le retour du rappel d’achèvement. Cela garantit que l’application a reçu les données/erreurs avant l’appel de la méthode Run .

Il est important de noter que l’application doit publier une autre lecture avant de retourner le contrôle à partir du rappel d’achèvement. Il est également important de noter que dataReader ne peut pas être utilisé directement avec le transport MessageWebSocket ou StreamWebSocket , car cela interrompt la synchronisation décrite ci-dessus. L’utilisation directe de la méthode DataReader.LoadAsync sur le transport n’est pas prise en charge. Au lieu de cela, le IBuffer retourné par la méthode IInputStream.ReadAsync sur la propriété StreamWebSocket.InputStream peut être transmis ultérieurement à la méthode DataReader.FromBuffer pour un traitement ultérieur.

L’exemple suivant montre comment utiliser un modèle asynchrone brut pour la gestion des lectures sur 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);
   }
}

Le gestionnaire d’achèvement de lecture est garanti d’être déclenché avant que la méthode IBackgroundTask.Run ne soit appelée dans la tâche d’arrière-plan associée à ControlChannelTrigger. Windows dispose d’une synchronisation interne pour attendre qu’une application revienne de la fonction de rappel de fin de lecture. L’application traite généralement rapidement les données ou l’erreur provenant de MessageWebSocket ou de StreamWebSocket dans la fonction de rappel de fin de lecture. Le message lui-même est traité dans le contexte de la méthode IBackgroundTask.Run . Dans l’exemple ci-dessous, ce point est illustré à l’aide d’une file d’attente de messages dans laquelle le gestionnaire de fin de lecture insère le message, que la tâche en arrière-plan traite ensuite.

L’exemple suivant montre le gestionnaire d’achèvement de lecture à utiliser avec un modèle asynchrone brut pour la gestion des lectures sur 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 détail supplémentaire concernant WebSocket est le gestionnaire de maintien de connexion. Le protocole WebSocket définit un modèle standard pour les messages keep-alive.

Lorsque vous utilisez MessageWebSocket ou StreamWebSocket, inscrivez une instance de classe WebSocketKeepAlive en tant que TaskEntryPoint pour un KeepAliveTrigger pour permettre à l’application d’être non attachée et d’envoyer régulièrement des messages keep-alive au serveur (point de terminaison distant). Cette opération doit être effectuée dans le cadre du code d’application d’inscription en arrière-plan, ainsi que dans le manifeste du package.

Ce point d’entrée de tâche de Windows. Sockets.WebSocketKeepAlive doit être spécifié à deux emplacements :

  • Lors de la création du déclencheur KeepAliveTrigger dans le code source (voir l’exemple ci-dessous).
  • Dans le manifeste du package d’application pour la déclaration de tâche en arrière-plan keepalive.

L’exemple suivant ajoute une notification de déclencheur réseau et un déclencheur keepalive sous l’élément <Application> dans un manifeste d’application.

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

Une application doit être extrêmement prudent lors de l’utilisation d’une instruction await dans le contexte d’un ControlChannelTrigger et d’une opération asynchrone sur un StreamWebSocket, MessageWebSocket ou StreamSocket. Un objet Task<bool> peut être utilisé pour enregistrer un ControlChannelTrigger pour les notifications push et les messages de maintien de connexion WebSocket sur le StreamWebSocket, et afin de connecter le transport. Dans le cadre de l’enregistrement, le transport StreamWebSocket est défini en tant que transport de ControlChannelTrigger et une opération de lecture est lancée. Task.Result bloque le thread actuel jusqu’à ce que toutes les étapes de la tâche exécutent et retournent des instructions dans le corps du message. La tâche n’est pas résolue tant que la méthode n’a pas retourné true ou false. Cela garantit que la méthode entière est exécutée. La tâche peut contenir plusieurs instructions await protégées par la tâche. Ce modèle doit être utilisé avec l’objet ControlChannelTrigger lorsqu’un StreamWebSocket ou MessageWebSocket est utilisé comme transport. Pour ces opérations qui peuvent prendre beaucoup de temps (une opération de lecture asynchrone classique, par exemple), l’application doit utiliser le modèle asynchrone brut décrit précédemment.

L’exemple suivant enregistre ControlChannelTrigger pour les notifications push et les messages de maintien en activité WebSocket sur le 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;
}

Pour plus d’informations sur l’utilisation de MessageWebSocket ou StreamWebSocket avec ControlChannelTrigger, consultez l’exemple StreamWebSocket ControlChannelTrigger.

ControlChannelTrigger avec HttpClient

Certaines considérations spéciales s’appliquent lors de l’utilisation de HttpClient avec ControlChannelTrigger. Il existe des modèles d’utilisation spécifiques au transport et des bonnes pratiques qui doivent être suivies lors de l’utilisation d’un HttpClient avec ControlChannelTrigger. En outre, ces considérations affectent la façon dont les demandes de réception des paquets sur HttpClient sont gérées.

RemarqueHttpClient utilisant SSL n’est actuellement pas pris en charge avec la fonctionnalité de déclencheur réseau et ControlChannelTrigger.   Les modèles d’utilisation et les bonnes pratiques suivants doivent être suivis lors de l’utilisation de HttpClient avec ControlChannelTrigger :

  • L’application peut avoir besoin de définir différentes propriétés et en-têtes sur l’objet HttpClient ou HttpClientHandler dans l’espace de noms System.Net.Http avant d’envoyer la requête à l’URI spécifique.
  • Une application peut avoir besoin d’une demande initiale pour tester et configurer le transport correctement avant de créer le transport HttpClient à utiliser avec ControlChannelTrigger. Une fois que l’application détermine que le transport peut être correctement configuré, un objet HttpClient peut être configuré comme objet de transport utilisé avec l’objet ControlChannelTrigger . Ce processus est conçu pour empêcher certains scénarios de rompre la connexion établie sur le transport. À l’aide du protocole SSL avec un certificat, une application peut nécessiter l’affichage d’une boîte de dialogue pour l’entrée de code confidentiel ou s’il existe plusieurs certificats à choisir. L’authentification proxy et l’authentification du serveur peuvent être requises. Si l’authentification proxy ou serveur expire, la connexion peut être fermée. Une façon dont une application peut traiter ces problèmes d’expiration d’authentification consiste à définir un minuteur. Lorsqu’une redirection HTTP est requise, elle n’est pas garantie que la deuxième connexion peut être établie de manière fiable. Une requête de test initiale permet de s’assurer que l’application peut utiliser l’URL de redirection la plus récente avant d’utiliser l’objet HttpClient comme transport avec l’objet ControlChannelTrigger.

Contrairement aux autres transports réseau, l’objet HttpClient ne peut pas être directement transmis à la méthode UsingTransport de l’objet ControlChannelTrigger . Au lieu de cela, un objet HttpRequestMessage doit être spécialement construit pour une utilisation avec l’objet HttpClient et ControlChannelTrigger. L’objet HttpRequestMessage est créé à l’aide de la méthode RtcRequestFactory.Create . L’objet HttpRequestMessage créé est ensuite passé à la méthode UsingTransport .

L’exemple suivant montre comment construire un objet HttpRequestMessage à utiliser avec l’objet HttpClient et 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;
    }
}

Certaines considérations spéciales affectent la façon dont les demandes d’envoi de requêtes HTTP sur HttpClient pour lancer la réception d’une réponse sont gérées. En particulier, lors de l’utilisation d’un HttpClient avec ControlChannelTrigger, votre application doit utiliser une tâche pour gérer les envois au lieu du modèle await .

Avec HttpClient, il n’y a aucune synchronisation avec la méthode IBackgroundTask.Run dans la tâche en arrière-plan du ControlChannelTrigger avec le retour du rappel d’achèvement de réception. Pour cette raison, l’application ne peut recourir qu’à la technique bloquante basée sur HttpResponseMessage dans la méthode Run et attendre que l’intégralité de la réponse soit reçue.

L’utilisation de HttpClient avec ControlChannelTrigger est sensiblement différente des transports StreamSocket, MessageWebSocket ou StreamWebSocket . Le rappel de réception HttpClient est remis via une tâche à l’application depuis le code HttpClient . Cela signifie que la tâche de notification Push ControlChannelTrigger se déclenche dès que les données ou l’erreur sont envoyées à l’application. Dans l’exemple ci-dessous, le code stocke la méthode responseTask retournée par la méthode HttpClient.SendAsync dans le stockage global que la tâche de notification Push récupère et traite en ligne.

L’exemple suivant montre comment gérer les requêtes d’envoi sur HttpClient lorsqu’elles sont utilisées avec 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);
}

L’exemple suivant montre comment lire les réponses reçues sur HttpClient lorsqu’elles sont utilisées avec 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;
}

Pour plus d’informations sur l’utilisation de HttpClient avec ControlChannelTrigger, consultez l’exemple HttpClient ControlChannelTrigger.

ControlChannelTrigger avec IXMLHttpRequest2

Certaines considérations spéciales s’appliquent lors de l’utilisation d’IXMLHTTPRequest2 avec ControlChannelTrigger. Il existe des modèles d’utilisation spécifiques au transport et des bonnes pratiques qui doivent être suivies lors de l’utilisation d’un IXMLHTTPRequest2 avec ControlChannelTrigger. L’utilisation de ControlChannelTrigger n’affecte pas la façon dont les demandes d’envoi ou de réception de requêtes HTTP sur IXMLHTTPRequest2 sont gérées.

Modèles d’utilisation et bonnes pratiques lors de l’utilisation d’IXMLHTTPRequest2 avec ControlChannelTrigger

  • Un objet IXMLHTTPRequest2 lorsqu’il est utilisé comme transport a une durée de vie d’une seule requête/réponse. Lorsqu’il est utilisé avec l’objet ControlChannelTrigger , il est pratique de créer et de configurer l’objet ControlChannelTrigger une fois, puis d’appeler la méthode UsingTransport à plusieurs reprises, chaque fois qu’il associe un nouvel objet IXMLHTTPRequest2 . Une application doit supprimer l’objet IXMLHTTPRequest2 précédent avant de fournir un nouvel objet IXMLHTTPRequest2 pour s’assurer que l’application ne dépasse pas les limites de ressources allouées.
  • L’application peut avoir besoin d’appeler les méthodes SetProperty et SetRequestHeader pour configurer le transport HTTP avant d’appeler la méthode Send .
  • Une application peut avoir besoin d’une demande d’envoi initiale pour tester et configurer le transport correctement avant de créer le transport à utiliser avec ControlChannelTrigger. Une fois que l’application détermine que le transport est correctement configuré, l’objet IXMLHTTPRequest2 peut être configuré comme objet de transport utilisé avec ControlChannelTrigger. Ce processus est conçu pour empêcher certains scénarios de rompre la connexion établie sur le transport. À l’aide du protocole SSL avec un certificat, une application peut nécessiter l’affichage d’une boîte de dialogue pour l’entrée de code confidentiel ou s’il existe plusieurs certificats à choisir. L’authentification proxy et l’authentification du serveur peuvent être requises. Si l’authentification proxy ou serveur expire, la connexion peut être fermée. Une façon dont une application peut traiter ces problèmes d’expiration d’authentification consiste à définir un minuteur. Lorsqu’une redirection HTTP est requise, elle n’est pas garantie que la deuxième connexion peut être établie de manière fiable. Une requête de test initiale garantit que l’application peut utiliser l’URL redirigée la plus récente avant d’utiliser l’objet IXMLHTTPRequest2 comme transport avec l’objet ControlChannelTrigger.

Pour plus d’informations sur l’utilisation d’IXMLHTTPRequest2 avec ControlChannelTrigger, consultez l’exemple ControlChannelTrigger avec IXMLHTTPRequest2.

API importantes