Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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 gniazdowy 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ę, gdy ruch dociera do gniazda; zwraca kontrolę aplikacji; a ta następnie przetwarza przychodzący ruch.
- Wyzwalacze kanału kontrolnego.
Wykonywanie operacji sieciowych w zadaniach w tle
- Użyj SocketActivityTrigger, aby aktywować zadanie w tle, gdy zostanie odebrany pakiet i należy wykonać zadanie krótkotrwałe. Po wykonaniu zadania zadanie w tle powinno zakończyć się w celu zaoszczędzenia zasilania.
- Użyj obiektu ControlChannelTrigger, aby aktywować zadanie w tle po odebraniu pakietu, gdy musisz wykonać długotrwałe zadanie.
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 wartość IsNetworkRequested w zadaniu w tle, aby upewnić się, że sieć pozostaje uruchomiona podczas uruchamiania zadania w tle. To mówi infrastrukturze zadań w tle, aby utrzymywała połączenie sieciowe podczas wykonywania zadania, nawet jeśli urządzenie weszło w tryb podtrzymania połączenia. Jeśli zadanie w tle nie używa funkcji IsNetworkRequested, zadanie w tle nie będzie mogło uzyskać dostępu do sieci w trybie wstrzymania połączonego (na przykład po wyłączeniu ekranu telefonu).
Broker gniazd oraz SocketActivityTrigger
Jeśli aplikacja używa połączeń DatagramSocket, StreamSocket lub StreamSocketListener, należy użyć SocketActivityTrigger i brokera gniazd, aby otrzymywać powiadomienia, gdy do aplikacji dociera ruch sieciowy, a aplikacja nie działa 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 element SocketActivityTrigger i zarejestruj zadanie w tle dla tego wyzwalacza, z parametrem TaskEntryPoint ustawionym na 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 metodę 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");
Po poprawnym skonfigurowaniu gniazda, gdy aplikacja zostanie wstrzymana, wywołaj usługę TransferOwnership na gniazdie, aby przenieść ją do brokera gniazd. Broker monitoruje gniazdo i uruchamia Twoje zadanie w tle po odebraniu danych. Poniższy przykład zawiera funkcję narzędziową TransferOwnership, służącą do przenoszenia gniazd typu 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:
- Jedna z metod TransferOwnership klasy DatagramSocket.
- Jedna z metod TransferOwnership klasy StreamSocket.
- Jedna z metod TransferOwnership klasy StreamSocketListener.
// declare int _transferOwnershipCount as a field.
private async void TransferOwnership(StreamSocketListener tcpListener)
{
await tcpListener.CancelIOAsync();
var dataWriter = new DataWriter();
++_transferOwnershipCount;
dataWriter.WriteInt32(_transferOwnershipCount);
var context = new SocketActivityContext(dataWriter.DetachBuffer());
tcpListener.TransferOwnership(_socketId, context);
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
TransferOwnership(_tcpListener);
deferral.Complete();
}
W programie obsługi zdarzeń zadania w tle:
- Najpierw uzyskaj odroczenie zadania w tle, aby można było obsłużyć zdarzenie przy użyciu 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 z powodu aktywności gniazda, utwórz obiekt DataReader dla gniazda, asynchronicznie załaduj dane do czytnika, a następnie użyj danych zgodnie z założeniami aplikacji. Należy pamiętać, aby przekazać z powrotem kontrolę nad gniazdem brokerowi gniazd, aby ponownie otrzymywać powiadomienia o kolejnej aktywności gniazda.
W poniższym przykładzie tekst odebrany z gniazda jest wyświetlany w powiadomieniu 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;
- 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. Ponownie należy pamiętać, że trzeba przekazać własność gniazda z powrotem brokerowi gniazd, aby otrzymywać kolejne 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 z powodu zamknięcia gniazda, utwórz gniazdo ponownie, 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 przedstawiającym użycie elementu 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 metodę TransferOwnership zaraz po utworzeniu nowego gniazda lub uzyskaniu dostępu do istniejącego gniazda, zamiast używać procedury obsługi zdarzenia OnSuspending, aby to zrobić, zgodnie z opisem w tym temacie. Jest to spowodowane tym, że przykład koncentruje się na demonstrowaniu elementu SocketActivityTrigger i nie używa gniazda dla żadnego innego działania podczas jego działania. Aplikacja będzie prawdopodobnie bardziej złożona i powinna używać funkcji OnSuspending , aby określić, kiedy wywołać usługę TransferOwnership.
Wyzwalacze kanału kontrolnego
Najpierw upewnij się, że używasz odpowiednio wyzwalaczy kanału sterowania (CCT). Jeśli używasz połączeń DatagramSocket, StreamSocket lub StreamSocketListener , zalecamy użycie modułu SocketActivityTrigger. Można używać technologii CCT dla StreamSocket, ale technologia ta zużywa więcej zasobów i może nie działać w trybie Connected Standby.
Jeśli używasz WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient lub Windows.Web.Http.HttpClient, musisz użyć ControlChannelTrigger.
ControlChannelTrigger z użyciem WebSocketów
Important
Funkcja opisana w tej sekcji (ControlChannelTrigger z WebSocketami) jest obsługiwana przez pakiet SDK systemu Windows w wersji 10.0.15063.0 i nowszych.
Niektóre specjalne zagadnienia mają zastosowanie podczas korzystania z funkcji MessageWebSocket lub StreamWebSocket z kontrolką 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 odbierania pakietów w usłudze MessageWebSocket nie mają wpływu.
Podczas korzystania z funkcji MessageWebSocket lub StreamWebSocket z kontrolką ControlChannelTrigger należy przestrzegać następujących wzorców użycia i najlepszych rozwiązań:
- Zaległe odbieranie gniazd musi być przechowywane przez cały czas. Jest to wymagane, aby umożliwić realizowanie zadań związanych z powiadomieniami push.
- Protokół WebSocket definiuje standardowy model komunikatów podtrzymujących połączenie. Klasa WebSocketKeepAlive może wysyłać do serwera komunikaty o zachowaniu aktywności inicjowane przez klienta protokołu WebSocket. Klasa WebSocketKeepAlive powinna być zarejestrowana jako TaskEntryPoint dla elementu KeepAliveTrigger przez aplikację.
Niektóre specjalne zagadnienia mają wpływ na sposób obsługi żądań odbierania pakietów w usłudze StreamWebSocket . W szczególności podczas używania obiektu StreamWebSocket z elementem ControlChannelTrigger aplikacja musi używać niskopoziomowego wzorca asynchronicznego do obsługi operacji odczytu zamiast modelu await w językach C# i VB.NET lub zadań w języku C++. Pierwotny wzorzec asynchroniczny jest zilustrowany w przykładzie kodu w dalszej części tej sekcji.
Użycie niskopoziomowego wzorca asynchronicznego umożliwia systemowi Windows synchronizację metody IBackgroundTask.Run w zadaniu w tle dla wyzwalacza ControlChannelTrigger z momentem powrotu wywołania zwrotnego informującego o zakończeniu odbierania. Metoda Run jest wywoływana po zakończeniu działania funkcji wywołania zwrotnego zakończenia. Gwarantuje to, że aplikacja odebrała dane/błędy przed wywołaniem metody Run .
Należy pamiętać, że aplikacja musi zainicjować kolejny odczyt, zanim zwróci sterowanie z funkcji zwrotnej zakończenia. Należy również pamiętać, że nie można bezpośrednio używać elementu DataReader z transportem MessageWebSocket lub StreamWebSocket , ponieważ przerywa synchronizację opisaną powyżej. Nie jest obsługiwane bezpośrednie użycie metody DataReader.LoadAsync na warstwie transportu. Zamiast tego właściwość IBuffer zwrócona przez metodę IInputStream.ReadAsync we właściwości StreamWebSocket.InputStream może zostać później przekazana do metody DataReader.FromBuffer w celu dalszego przetwarzania.
W poniższym przykładzie pokazano, jak używać nieprzetworzonego wzorca asynchronicznego do obsługi odczytów w usłudze 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);
}
}
Gwarantowane jest, że procedura obsługi zakończenia odczytu zostanie wywołana przed wywołaniem metody IBackgroundTask.Run w zadaniu w tle dla elementu ControlChannelTrigger. System Windows ma mechanizm synchronizacji wewnętrznej, aby zaczekać, aż aplikacja zakończy wykonywanie funkcji wywołania zwrotnego po zakończeniu odczytu. Aplikacja zwykle szybko przetwarza dane lub błąd z MessageWebSocket albo StreamWebSocket w wywołaniu zwrotnym po zakończeniu odczytu. Sam komunikat jest przetwarzany w kontekście metody IBackgroundTask.Run . W poniższym przykładzie zilustrowano to przy użyciu kolejki komunikatów, do której procedura obsługi zakończenia odczytu wstawia komunikat, a zadanie działające w tle przetwarza go później.
Poniższy przykład przedstawia procedurę obsługi zakończenia odczytu do użycia z surowym wzorcem asynchronicznym do obsługi odczytów w obiekcie 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 elementem w przypadku protokołu WebSocket jest mechanizm keep-alive. Protokół WebSocket definiuje standardowy model komunikatów podtrzymujących połączenie.
W przypadku używania klasy MessageWebSocket lub klasy StreamWebSocket zarejestruj wystąpienie klasy WebSocketKeepAlive jako TaskEntryPoint dla wyzwalacza KeepAliveTrigger, aby umożliwić wybudzenie aplikacji ze stanu wstrzymania i okresowe wysyłanie do serwera (zdalnego punktu końcowego) komunikatów keep-alive. Należy to zrobić zarówno w kodzie aplikacji rejestracji działającej w tle, jak i w manifeście pakietu.
Ten punkt wejścia zadania Windows. Sockets.WebSocketKeepAlive należy określić w dwóch miejscach:
- Podczas tworzenia wyzwalacza KeepAliveTrigger w kodzie źródłowym (zobacz przykład poniżej).
- W manifeście pakietu aplikacji, w deklaracji zadania keepalive działającego w tle.
Poniższy przykład dodaje powiadomienie wyzwalane przez sieć oraz wyzwalacz podtrzymania połączenia w elemencie <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ść podczas używania instrukcji await w kontekście elementu ControlChannelTrigger oraz operacji asynchronicznej na obiekcie StreamWebSocket, MessageWebSocket lub StreamSocket. Obiekt Task<bool> może służyć do zarejestrowania elementu ControlChannelTrigger na potrzeby powiadomień push i mechanizmów utrzymywania aktywności WebSocket w obiekcie StreamWebSocket oraz do nawiązania połączenia transportowego. W ramach rejestracji transport StreamWebSocket jest ustawiony jako transport controlChannelTrigger i odczyt jest publikowany. Element Task.Result zablokuje bieżący wątek do momentu wykonania wszystkich kroków zadania i zwrócenia instrukcji 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. Zadanie może zawierać wiele instrukcji await, które są objęte ochroną przez element Task. 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 element ControlChannelTrigger na potrzeby powiadomień push i funkcji WebSocket keep-alives w usłudze 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 elementu MessageWebSocket lub StreamWebSocket z kontrolką ControlChannelTrigger, zobacz przykład ControlChannelTrigger StreamWebSocket.
ControlChannelTrigger przy użyciu klienta HttpClient
Podczas korzystania z obiektu HttpClient z kontrolką ControlChannelTrigger obowiązują 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 kwestie wpływają na sposób obsługi żądań dotyczących odbierania pakietów przez obiekt HttpClient.
UwagaHttpClient korzystający z protokołu SSL nie jest obecnie obsługiwany przy użyciu funkcji wyzwalacza sieciowego i ControlChannelTrigger. Podczas korzystania z obiektu HttpClient z kontrolką 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ć wysłania początkowego żądania w celu przetestowania i prawidłowego skonfigurowania transportu przed utworzeniem transportu HttpClient, który będzie 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 ma na celu zapobieganie sytuacjom, które mogłyby doprowadzić do przerwania połączenia ustanowionego na poziomie transportu. 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 serwera proxy i uwierzytelnianie serwera. Jeśli uwierzytelnianie serwera lub 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 zapewni, że aplikacja będzie mogła używać najbardziej aktualnego przekierowanego adresu URL przed użyciem obiektu HttpClient jako transportu dla obiektu ControlChannelTrigger.
W przeciwieństwie do innych transportów sieciowych obiekt HttpClient nie może być bezpośrednio przekazywany 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 przy użyciu metody RtcRequestFactory.Create . Utworzony obiekt HttpRequestMessage jest następnie przekazywany do metody UsingTransport .
W poniższym przykładzie pokazano, jak skonstruować obiekt HttpRequestMessage do użycia z obiektem HttpClient i kontrolką 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;
}
}
Pewne szczególne kwestie wpływają na sposób obsługi żądań wysłania żądań HTTP za pomocą HttpClient w celu rozpoczęcia odbierania odpowiedzi. W szczególności podczas korzystania z obiektu HttpClient w połączeniu z ControlChannelTrigger aplikacja musi używać elementu Task do obsługi operacji wysyłania zamiast modelu await.
W przypadku użycia HttpClient nie ma synchronizacji metody IBackgroundTask.Run w zadaniu w tle dla ControlChannelTrigger ze zwrotem wywołania zwrotnego sygnalizującego zakończenie odbioru. Z tego powodu aplikacja może używać tylko blokującej techniki HttpResponseMessage w metodzie Run oraz czekać, aż cała odpowiedź zostanie odebrana.
Korzystanie z HttpClient z ControlChannelTrigger znacznie różni się od transportów StreamSocket, MessageWebSocket lub StreamWebSocket. Wywołanie zwrotne odbierania HttpClient jest przekazywane do aplikacji za pośrednictwem obiektu Task, ponieważ kod HttpClient. Oznacza to, że zadanie powiadomienia push ControlChannelTrigger zostanie uruchomione natychmiast po dostarczeniu do aplikacji danych lub informacji o błędzie. W poniższym przykładzie kod zapisuje element responseTask zwrócony przez metodę HttpClient.SendAsync w magazynie globalnym, skąd zadanie obsługujące powiadomienia wypychane pobierze go i przetworzy bezpośrednio.
Poniższy przykład pokazuje, jak obsługiwać wysyłanie żądań za pomocą obiektu HttpClient, gdy jest on używany 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);
}
Poniższy przykład pokazuje, jak odczytywać odpowiedzi odbierane za pomocą klienta HttpClient podczas używania go z elementem 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 obiektu HttpClient z kontrolką ControlChannelTrigger, zobacz przykład ControlChannelTrigger HttpClient.
ControlChannelTrigger z IXMLHttpRequest2
Podczas korzystania z IXMLHTTPRequest2 z kontrolką ControlChannelTrigger należy wziąć pod uwagę pewne specjalne zagadnienia. 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 metody ControlChannelTrigger nie wpływa na sposób obsługi żądań wysyłania lub odbierania żądań HTTP w żądaniu IXMLHTTPRequest2 .
Wzorce użycia i najlepsze praktyki podczas 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 Send, aby przetestować i prawidłowo skonfigurować transport przed utworzeniem 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 kontrolką ControlChannelTrigger. Proces ten ma zapobiegać temu, by niektóre sytuacje przerywały połączenie ustanowione za pośrednictwem warstwy transportowej. 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 serwera proxy i uwierzytelnianie serwera. Jeśli uwierzytelnianie serwera proxy lub serwera 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 zapewni, że aplikacja będzie mogła użyć najbardziej aktualnego przekierowanego adresu URL, zanim użyje obiektu IXMLHTTPRequest2 jako mechanizmu transportu z obiektem ControlChannelTrigger.
Aby uzyskać więcej informacji na temat używania IXMLHTTPRequest2 z ControlChannelTrigger, zobacz przykład „ControlChannelTrigger z IXMLHTTPRequest2”.