Udostępnij przez


Komunikacja sieciowa w tle

Aby kontynuować komunikację sieci, gdy nie znajduje się ona na pierwszym planie, aplikacja może używać zadań w tle i jednej z tych dwóch opcji.

  • Broker połączeń gniazdowych. Jeśli aplikacja używa gniazd dla połączeń długoterminowych, wówczas, gdy opuszcza pierwszy plan, może delegować własność gniazda do brokera gniazd systemowych. Następnie broker: aktywuje aplikację po nadejściu ruchu sieciowego do gniazda; przekazuje kontrolę z powrotem do aplikacji; a następnie aplikacja przetwarza przychodzący ruch.
  • Wyzwalacze kanału sterowania.

Wykonywanie operacji sieciowych w zadaniach w tle

  • Użyj SocketActivityTrigger, aby aktywować zadanie w tle po odebraniu pakietu i aby wykonać zadanie krótkotrwałe. Po wykonaniu zadania zadanie w tle powinno zakończyć się w celu zaoszczędzenia zasilania.
  • Użyj ControlChannelTrigger, aby aktywować zadanie działające w tle po odebraniu pakietu i potrzebie wykonania długotrwałego zadania.

Warunki i flagi związane z siecią

  • Dodaj warunek InternetAvailable do zadania w tle BackgroundTaskBuilder.AddCondition, aby opóźnić wyzwalanie zadania w tle do momentu uruchomienia stosu sieciowego. Ten warunek oszczędza energię, ponieważ zadanie w tle nie zostanie wykonane, dopóki sieć nie zostanie uruchomiona. Ten warunek nie zapewnia aktywacji w czasie rzeczywistym.

Niezależnie od używanego wyzwalacza ustaw IsNetworkRequested dla zadania w tle, aby zapewnić, że sieć była aktywna podczas jego działania. Spowoduje to, że infrastruktura zadań działających w tle będzie utrzymywać połączenie sieciowe w trakcie wykonywania zadania, nawet jeśli urządzenie weszło w tryb połączonego wstrzymania. Jeśli twoje zadanie w tle nie używa IsNetworkRequested, nie będzie mogło uzyskać dostępu do sieci w trybie podtrzymania połączenia (na przykład gdy telefon ma wyłączony ekran).

Broker połączeń gniazd i Aktywator działania gniazda

Jeśli Twoja aplikacja używa DatagramSocket, StreamSocketlub StreamSocketListener, należy użyć SocketActivityTrigger i brokera gniazd, aby być powiadamianym o ruchu docierającym do aplikacji, gdy nie jest na pierwszym planie.

Aby aplikacja odbierała i przetwarzała dane odebrane w gniazdie, gdy aplikacja nie jest aktywna, aplikacja musi wykonać jednorazową konfigurację podczas uruchamiania, a następnie przenieść własność gniazd do brokera gniazd, gdy przechodzi do stanu, w którym nie jest aktywna.

Jednorazowe kroki konfiguracji polegają na utworzeniu wyzwalacza, zarejestrowaniu zadania w tle dla wyzwalacza i włączeniu gniazda dla brokera gniazd:

  • Utwórz SocketActivityTrigger i zarejestruj zadanie w tle powiązane z wyzwalaczem, ustawiając parametr TaskEntryPoint na swój kod do przetwarzania odebranego pakietu.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Przed powiązaniem gniazda wywołaj EnableTransferOwnership.
           _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");

Gdy twoje gniazdo jest prawidłowo skonfigurowane i gdy aplikacja ma zostać wstrzymana, wywołaj TransferOwnership na gnieździe, aby przenieść je do brokera gniazd. Broker monitoruje gniazdo i aktywuje zadanie w tle po odebraniu danych. W poniższym przykładzie przedstawiono funkcję TransferOwnership do wykonania przekazania własności gniazdom StreamSocketListener. (Należy pamiętać, że różne typy gniazd mają własną metodę TransferOwnership , więc należy wywołać metodę odpowiednią dla gniazda, którego własność jest przenoszona. Kod prawdopodobnie zawiera przeciążony pomocnik TransferOwnership z jedną implementacją dla każdego używanego typu gniazda, dzięki czemu kod OnSuspending pozostaje łatwy do odczytania.

Aplikacja przenosi własność gniazda do brokera gniazd i przekazuje identyfikator zadania w tle przy użyciu jednej z następujących metod:


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

W programie obsługi zdarzeń zadania w tle:

  • Najpierw uzyskaj odroczenie zadania w tle, aby móc obsłużyć zdarzenie za pomocą metod asynchronicznych.
var deferral = taskInstance.GetDeferral();
  • Następnie wyodrębnij element SocketActivityTriggerDetails z argumentów zdarzenia i znajdź przyczynę zgłoszenia zdarzenia:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Jeśli zdarzenie zostało wywołane przez aktywność gniazda, utwórz DataReader na gnieździe, załaduj czytnik asynchronicznie, a następnie użyj danych zgodnie z projektem twojej aplikacji. Należy pamiętać, że należy ponownie przekazać własność gniazda do brokera gniazd, aby ponownie otrzymywać powiadomienia o dalszym działaniu gniazda.

W poniższym przykładzie tekst otrzymany na gniazdie jest wyświetlany w wyskakującej wiadomości.

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;
  • Jeśli zdarzenie zostało zgłoszone, ponieważ czasomierz utrzymania aktywności wygasł, kod powinien wysłać pewne dane przez gniazdo w celu zachowania aktywności gniazda i ponownego uruchomienia czasomierza utrzymania aktywności. Ważne jest ponowne zwrócenie własności gniazda brokerowi gniazd, aby otrzymywać dalsze powiadomienia o zdarzeniach.
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;
  • Jeśli zdarzenie zostało zgłoszone, ponieważ gniazdo zostało zamknięte, ponownie skonfiguruj gniazdo, upewniając się, że po utworzeniu nowego gniazda przeniesiesz jego własność do brokera gniazd. W tym przykładzie nazwa hosta i port są przechowywane w ustawieniach lokalnych, aby można było użyć ich do ustanowienia nowego połączenia gniazda:
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;
  • Nie zapomnij ukończyć odroczenia, po zakończeniu przetwarzania powiadomienia o zdarzeniu:
  deferral.Complete();

Aby zapoznać się z kompletnym przykładem użycia SocketActivityTrigger i brokera gniazd, zobacz przykład SocketActivityStreamSocket. Inicjowanie gniazda jest wykonywane w Scenario1_Connect.xaml.cs, a implementacja zadań w tle znajduje się w SocketActivityTask.cs.

Prawdopodobnie zauważysz, że przykład wywołuje TransferOwnership zaraz po utworzeniu lub nabyciu istniejącego gniazda, zamiast korzystania z programu obsługi zdarzenia OnSuspending, tak jak opisano w tym temacie. Jest to spowodowane tym, że przykład koncentruje się na demonstrowaniu SocketActivityTriggeri nie używa gniazda do żadnej innej czynności podczas jego działania. Twoja aplikacja będzie prawdopodobnie bardziej złożona i powinna używać OnSuspending, aby określić, kiedy wywołać TransferOwnership.

Wyzwalacze kanału sterowania

Najpierw upewnij się, że używasz odpowiednio wyzwalaczy kanału sterowania (CCT). Jeśli używasz połączeń DatagramSocket, StreamSocketlub StreamSocketListener, zalecamy użycie SocketActivityTrigger. Można używać CCT dla StreamSocket, ale korzystają one z większej liczby zasobów i mogą nie działać w trybie połączonego wstrzymania.

Jeśli używasz WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClientlub Windows.Web.Http.HttpClient, musisz użyć ControlChannelTrigger.

ControlChannelTrigger z użyciem WebSocketów

Ważne

Funkcja opisana w tej sekcji (ControlChannelTrigger przy użyciuWebSockets) jest obsługiwana w wersji 10.0.15063.0 zestawu SDK oraz we wcześniejszych wersjach. Jest on również obsługiwany w wersjach wstępnych Windows 10 Insider Preview.

W przypadku korzystania z MessageWebSocket lub StreamWebSocket z ControlChannelTrigger. Istnieją pewne wzorce użycia specyficzne dla transportu i najlepsze rozwiązania, które należy stosować podczas korzystania z elementu MessageWebSocket lub StreamWebSocket z kontrolką ControlChannelTrigger. Ponadto te zagadnienia wpływają na sposób obsługi żądań odbierania pakietów w usłudze StreamWebSocket . Żądania nawiązywania połączeń na MessageWebSocket pozostają bez zmian.

Podczas korzystania z funkcji MessageWebSocket lub StreamWebSocket z kontrolką ControlChannelTrigger należy przestrzegać następujących wzorców użycia i najlepszych rozwiązań:

  • Należy zapewnić ciągłe monitorowanie przyjmowania danych przez gniazdo. Jest to wymagane, aby umożliwić wykonywanie zadań powiadomień push.
  • Protokół WebSocket definiuje standardowy model dla komunikatów utrzymujących połączenie. Klasa WebSocketKeepAlive może wysyłać do serwera komunikaty o zachowaniu aktywności inicjowane przez klienta protokołu WebSocket. W aplikacji klasa WebSocketKeepAlive powinna zostać zarejestrowana jako TaskEntryPoint dla elementu KeepAliveTrigger.

Niektóre specjalne zagadnienia mają wpływ na sposób obsługi żądań odbierania pakietów w usłudze StreamWebSocket . W szczególności w przypadku używania StreamWebSocket z ControlChannelTriggeraplikacja musi używać nieprzetworzonego wzorca asynchronicznego do obsługi odczytów zamiast modelu await w języku C# i VB.NET lub Tasks w języku C++. Pierwotny wzorzec asynchroniczny jest zilustrowany w przykładzie kodu w dalszej części tej sekcji.

Użycie nieprzetworzonego wzorca asynchronicznego pozwala systemowi Windows na synchronizację metody IBackgroundTask.Run w ramach zadania w tle dla ControlChannelTrigger z momentem zakończenia wywołania zwrotnego odbioru. Metoda Run jest wywoływana po powrocie wywołania zwrotnego po zakończeniu. Gwarantuje to, że aplikacja odebrała dane/błędy przed wywołaniem metody Run .

Należy pamiętać, że aplikacja musi wykonać kolejny odczyt przed zwróceniem kontroli z wywołania zwrotnego ukończenia. Należy również pamiętać, że DataReader nie może być bezpośrednio używany z MessageWebSocket lub StreamWebSocket transportem, ponieważ przerywa synchronizację opisaną powyżej. Nie jest obsługiwane używanie metody DataReader.LoadAsync bezpośrednio na transporcie. Zamiast tego, obiekt IBuffer zwrócony przez metodę IInputStream.ReadAsync na właściwości StreamWebSocket.InputStream można później przekazać do metody DataReader.FromBuffer w celu dalszego przetwarzania.

W poniższym przykładzie pokazano, jak stosować surowy wzorzec asynchroniczny do obsługi odczytów w 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);
   }
}

Program obsługi uzupełniania odczytu ma gwarancję uruchomienia przed wywołaniem metody IBackgroundTask.Run w zadaniu w tle dla ControlChannelTrigger. System Windows posiada mechanizm wewnętrznej synchronizacji, który umożliwia oczekiwanie, aż aplikacja powróci z wywołania zwrotnego zakończenia odczytu. Aplikacja zazwyczaj szybko przetwarza dane lub błąd z MessageWebSocket lub StreamWebSocket w wywołaniu zwrotnym kończącym odczyt. Sam komunikat jest przetwarzany w kontekście metody IBackgroundTask.Run . W poniższym przykładzie ten punkt jest ilustrowany poprzez użycie kolejki komunikatów, do której program obsługi uzupełniania odczytu wstawia komunikat, a zadanie w tle przetwarza go później.

W poniższym przykładzie przedstawiono procedurę obsługi zakończenia odczytu do użycia z nieprzetworzonym wzorcem asynchronicznym do obsługi operacji odczytu w 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);
}

Dodatkowym szczegółem dotyczącym Websockets jest mechanizm keep-alive. Protokół WebSocket definiuje standardowy model dla komunikatów utrzymujących połączenie.

W przypadku korzystania z MessageWebSocket lub StreamWebSocket, zarejestruj wystąpienie klasy WebSocketKeepAlive jako wystąpienie klasy TaskEntryPoint dla elementu KeepAliveTrigger, aby zezwolić aplikacji na okres odłączenia aplikacji i wysyłania komunikatów o zachowaniu aktywności na serwerze (zdalny punkt końcowy). Należy to zrobić w ramach kodu aplikacji rejestracyjnej działającej w tle, a także w manifeście pakietu.

Ten punkt wejścia zadania Windows.Sockets.WebSocketKeepAlive musi być określony w dwóch miejscach:

  • Podczas tworzenia wyzwalacza KeepAliveTrigger w kodzie źródłowym (zobacz przykład poniżej).
  • W manifeście pakietu aplikacji dla deklaracji w tle zadania keepalive.

Poniższy przykład dodaje powiadomienie wyzwalacza sieciowego oraz wyzwalacz podtrzymania połączenia pod elementem <Application> w manifeście aplikacji.

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

Aplikacja musi zachować szczególną ostrożność przy używaniu instrukcji await w kontekście ControlChannelTrigger oraz operacji asynchronicznej na StreamWebSocket, MessageWebSocketlub StreamSocket. Obiekt zadania<typu logicznego> można użyć do rejestrowania ControlChannelTrigger na potrzeby powiadomień push i utrzymywania aktywności WebSocket na StreamWebSocket oraz do łączenia transportu. W ramach rejestracji transport StreamWebSocket jest ustawiony jako transport dla ControlChannelTrigger i odczyt jest publikowany. Task.Result zablokuje bieżący wątek do momentu, aż wszystkie kroki w zadaniu zostaną wykonane, a instrukcje zwrotne pojawią się w treści komunikatu. Zadanie nie zostanie rozwiązane, dopóki metoda nie zwróci wartości true lub false. Gwarantuje to wykonanie całej metody. zadania może zawierać wiele instrukcji await chronionych przezTask. Ten wzorzec powinien być używany z obiektem ControlChannelTrigger , gdy element StreamWebSocket lub MessageWebSocket jest używany jako transport. W przypadku tych operacji, które mogą potrwać długi czas (na przykład typowa operacja odczytu asynchronicznego), aplikacja powinna używać wcześniej omówionego wzorca asynchronicznego.

Poniższy przykład rejestruje ControlChannelTrigger na potrzeby powiadomień push i utrzymywania aktywności protokołu WebSocket przy użyciu 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
}

Aby uzyskać więcej informacji na temat używania MessageWebSocket lub StreamWebSocket z ControlChannelTrigger, zobacz przykład ControlChannelTrigger StreamWebSocket.

ControlChannelTrigger z klientem HttpClient

Podczas używania HttpClient z ControlChannelTriggermają zastosowanie pewne specjalne zagadnienia. Istnieją pewne wzorce użycia specyficzne dla transportu i najlepsze rozwiązania, które należy stosować podczas korzystania z obiektu HttpClient z kontrolką ControlChannelTrigger. Ponadto te zagadnienia wpływają na sposób, w jaki obsługiwane są żądania odbierania pakietów na HttpClient.

UwagaHttpClient z użyciem SSL nie jest obecnie obsługiwany przez funkcję wyzwalacza sieciowego i ControlChannelTrigger.   Podczas korzystania z HttpClient z ControlChannelTrigger należy przestrzegać następujących wzorców użycia i najlepszych rozwiązań:

  • Aplikacja może wymagać ustawienia różnych właściwości i nagłówków w obiekcie HttpClient lub HttpClientHandler w przestrzeni nazw System.Net.Http przed wysłaniem żądania do określonego identyfikatora URI.
  • Aplikacja może wymagać początkowego żądania, aby przetestować i odpowiednio skonfigurować transport przed utworzeniem transportu HttpClient, który ma być używany z ControlChannelTrigger. Gdy aplikacja ustali, że transport może być prawidłowo skonfigurowany, obiekt HttpClient można skonfigurować jako obiekt transportu używany z obiektem ControlChannelTrigger . Ten proces jest zaprojektowany, aby zapobiec scenariuszom, które mogą przerwać połączenie ustanowione przez transport. Przy użyciu protokołu SSL z certyfikatem aplikacja może wymagać wyświetlenia okna dialogowego dla wpisu numeru PIN lub jeśli istnieje wiele certyfikatów do wyboru. Może być wymagane uwierzytelnianie proxy i uwierzytelnianie serwera. Jeśli uwierzytelnianie serwera proxy wygaśnie, połączenie może zostać zamknięte. Jednym ze sposobów, w jaki aplikacja może poradzić sobie z tymi problemami z wygaśnięciem uwierzytelniania, jest ustawienie czasomierza. Gdy wymagane jest przekierowanie HTTP, nie ma gwarancji, że drugie połączenie można niezawodnie ustanowić. Wstępne żądanie testowe gwarantuje, że aplikacja może używać najbardziej up-to-date przekierowanego adresu URL przed użyciem obiektu HttpClient jako transportu z obiektem ControlChannelTrigger.

W przeciwieństwie do innych transportów sieciowych, obiekt HttpClient nie może być bezpośrednio przekazany do metody UsingTransport obiektu ControlChannelTrigger. Zamiast tego obiekt HttpRequestMessage musi być specjalnie skonstruowany do użytku z obiektem HttpClient i kontrolką ControlChannelTrigger. Obiekt HttpRequestMessage jest tworzony za pomocą metody RtcRequestFactory.Create. Utworzony obiekt HttpRequestMessage jest następnie przekazywany do metody UsingTransport .

W poniższym przykładzie pokazano, jak skonstruować obiekt HttpRequestMessage do zastosowania z obiektem HttpClient oraz z 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;
    }
}

Niektóre specjalne względy wpływają na sposób, w jaki żądania o wysłanie zapytań HTTP przy użyciu obiektu HttpClient są obsługiwane, aby zainicjować odbiór odpowiedzi. W szczególności w przypadku korzystania z HttpClient z ControlChannelTriggeraplikacja musi używać zadania do obsługi wysyłania zamiast await modelu.

Przy użyciu HttpClient nie ma synchronizacji z metodą IBackgroundTask.Run w zadaniu w tle dla ControlChannelTrigger przy przekazywaniu zwrotu wywołania ukończenia odbioru. Z tego powodu aplikacja może używać wyłącznie blokującej techniki HttpResponseMessage w metodzie Run i poczekać na odebranie całej odpowiedzi.

Używanie HttpClient z ControlChannelTrigger znacznie różni się od transportów takich jak StreamSocket, MessageWebSocket lub StreamWebSocket. HttpClient odbieranie wywołania zwrotnego jest przekazywane do aplikacji za pośrednictwem zadania, co wynika z kodu HttpClient. Zadanie ControlChannelTrigger powiadomień push zostanie uruchomione natychmiast po wysłaniu danych lub błędu do aplikacji. W poniższym przykładzie kod przechowuje obiekt responseTask zwrócony przez metodę HttpClient.SendAsync w magazynie globalnym, skąd zadanie powiadomień push odbierze go i przetworzy bezpośrednio w kodzie.

W poniższym przykładzie pokazano, jak obsługiwać wysyłanie żądań w HttpClient w przypadku użycia z 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);
}

W poniższym przykładzie pokazano, jak odczytywać odpowiedzi odebrane na HttpClient, gdy jest używany z 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;
}

Aby uzyskać więcej informacji na temat używania HttpClient z ControlChannelTrigger, zobacz próbkę ControlChannelTrigger HttpClient.

ControlChannelTrigger z IXMLHttpRequest2

W przypadku używania IXMLHTTPRequest2 z ControlChannelTriggernależy wziąć pod uwagę kilka szczególnych zagadnień. Istnieją pewne wzorce użycia specyficzne dla transportu i najlepsze rozwiązania, które należy stosować podczas korzystania z IXMLHTTPRequest2 z kontrolką ControlChannelTrigger. Użycie ControlChannelTrigger nie ma wpływu na sposób obsługi żądań wysyłania lub odbierania HTTP na IXMLHTTPRequest2.

Wzorce użycia i najlepsze praktyki dotyczące używania IXMLHTTPRequest2 z ControlChannelTrigger

  • Obiekt IXMLHTTPRequest2 używany jako transport ma okres istnienia tylko jednego żądania/odpowiedzi. W przypadku użycia z obiektem ControlChannelTrigger wygodne jest utworzenie i skonfigurowanie obiektu ControlChannelTrigger raz, a następnie wielokrotne wywołanie metody UsingTransport przy każdym skojarzeniu nowego obiektu IXMLHTTPRequest2 . Aplikacja powinna usunąć poprzedni obiekt IXMLHTTPRequest2 przed dostarczeniem nowego obiektu IXMLHTTPRequest2 , aby upewnić się, że aplikacja nie przekracza przydzielonych limitów zasobów.
  • Aplikacja może wymagać wywołania metod SetProperty i SetRequestHeader w celu skonfigurowania transportu HTTP przed wywołaniem metody Send .
  • Aplikacja może wymagać wysłania początkowego żądania , aby przetestować i prawidłowo skonfigurować transport przed utworzeniem tego transportu, który ma być używany z ControlChannelTrigger. Gdy aplikacja ustali, że transport jest prawidłowo skonfigurowany, obiekt IXMLHTTPRequest2 można skonfigurować jako obiekt transportu używany z ControlChannelTrigger. Ten proces jest zaprojektowany, aby zapobiec scenariuszom, które mogą przerwać połączenie ustanowione przez transport. Przy użyciu protokołu SSL z certyfikatem aplikacja może wymagać wyświetlenia okna dialogowego dla wpisu numeru PIN lub jeśli istnieje wiele certyfikatów do wyboru. Może być wymagane uwierzytelnianie proxy i uwierzytelnianie serwera. Jeśli uwierzytelnianie serwera proxy wygaśnie, połączenie może zostać zamknięte. Jednym ze sposobów, w jaki aplikacja może poradzić sobie z tymi problemami z wygaśnięciem uwierzytelniania, jest ustawienie czasomierza. Gdy wymagane jest przekierowanie HTTP, nie ma gwarancji, że drugie połączenie można niezawodnie ustanowić. Wstępne żądanie testowe gwarantuje, że aplikacja może używać najbardziej up-to-date przekierowanego adresu URL przed użyciem obiektu IXMLHTTPRequest2 jako transportu z obiektem ControlChannelTrigger.

Aby uzyskać więcej informacji na temat używania IXMLHTTPRequest2 z ControlChannelTrigger, zobacz ControlChannelTrigger with IXMLHTTPRequest2 sample.

Ważne interfejsy API