Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Per continuare la comunicazione di rete mentre non è in primo piano, l'app può usare le attività in background e una di queste due opzioni.
- Gestore socket. Se l'app usa i socket per connessioni a lungo termine, quando passa in background può delegare la proprietà di un socket a un broker di socket di sistema. Il broker quindi: attiva l'app quando il traffico arriva sul socket; restituisce la proprietà all'app; e l'app elabora il traffico in arrivo.
- Attivatori del canale di controllo.
Esecuzione di operazioni di rete nelle attività in background
- Usare un SocketActivityTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di breve durata. Dopo aver eseguito l'attività, l'attività in background deve terminare per risparmiare energia.
- Usare un ControlChannelTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di lunga durata.
Condizioni e flag correlati alla rete
- Aggiungere la condizione InternetAvailable all'attività in background utilizzando BackgroundTaskBuilder.AddCondition per ritardare l'attivazione dell'attività in background fino all'esecuzione dello stack di rete. Questa condizione consente di risparmiare energia perché l'attività in background non verrà eseguita finché la rete non è attiva. Questa condizione non fornisce l'attivazione in tempo reale.
Indipendentemente dal trigger usato, imposta IsNetworkRequested nell'attività in background per assicurarti che la rete rimanga attiva durante l'esecuzione dell'attività in background. Ciò indica all'infrastruttura delle attività in background di mantenere attiva la rete mentre l'attività è in esecuzione, anche se il dispositivo è entrato in modalità Standby connesso. Se l'attività in background non usa IsNetworkRequested, l'attività in background non sarà in grado di accedere alla rete in modalità Standby connesso( ad esempio, quando lo schermo di un telefono è disattivato).
Broker di socket e SocketActivityTrigger
Se l'app usa connessioni DatagramSocket, StreamSocket o StreamSocketListener , devi usare SocketActivityTrigger e il gestore socket per ricevere una notifica quando arriva il traffico per la tua app mentre non è in primo piano.
Per consentire all'app di ricevere ed elaborare i dati ricevuti su un socket quando l'app non è attiva, l'app deve eseguire alcune operazioni di installazione una tantum all'avvio e quindi trasferire la proprietà del socket al gestore socket quando passa a uno stato in cui non è attivo.
I passaggi di installazione una tantum sono la creazione di un trigger, la registrazione di un'attività in background per il trigger e l'abilitazione del socket per il gestore del socket:
- Creare un socketActivityTrigger e registrare un'attività in background per il trigger con il parametro TaskEntryPoint impostato sul codice per l'elaborazione di un pacchetto ricevuto.
var socketTaskBuilder = new BackgroundTaskBuilder();
socketTaskBuilder.Name = _backgroundTaskName;
socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
var trigger = new SocketActivityTrigger();
socketTaskBuilder.SetTrigger(trigger);
_task = socketTaskBuilder.Register();
- Chiama EnableTransferOwnership sul socket prima di eseguire il binding del 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");
Una volta configurato correttamente il socket, quando l'app sta per sospendere, chiama TransferOwnership sul socket per trasferirlo a un gestore socket. Il broker monitora il socket e attiva la tua attività in secondo piano quando vengono ricevuti dati. L'esempio seguente include una funzione di utilità TransferOwnership per effettuare il trasferimento dei socket StreamSocketListener. Si noti che i diversi tipi di socket hanno il proprio metodo TransferOwnership , quindi è necessario chiamare il metodo appropriato per il socket di cui si sta trasferendo la proprietà. Il codice conterrà probabilmente un helper TransferOwnership di overload con un'implementazione per ogni tipo di socket usato, in modo che il codice OnSuspending rimanga facile da leggere.
Un'app trasferisce la proprietà di un socket a un gestore socket e passa l'ID per l'attività in background usando uno dei metodi seguenti:
- Uno dei metodi TransferOwnership di un DatagramSocket.
- Uno dei metodi TransferOwnership di un StreamSocket.
- Uno dei metodi TransferOwnership in 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();
}
Nel gestore eventi dell'attività in background:
- Prima di tutto, ottenere un rinvio dell'attività in background in modo da poter gestire l'evento usando metodi asincroni.
var deferral = taskInstance.GetDeferral();
- Estrarre quindi SocketActivityTriggerDetails dagli argomenti dell'evento e individuare il motivo per cui è stato generato l'evento:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
var socketInformation = details.SocketInformation;
switch (details.Reason)
- Se l'evento è stato generato a causa dell'attività del socket, creare un oggetto DataReader sul socket, caricare il lettore in modo asincrono e quindi usare i dati in base alla progettazione dell'app. Si noti che è necessario restituire la proprietà del socket al broker del socket, in modo da ricevere nuovamente notifiche di ulteriore attività del socket.
Nell'esempio seguente il testo ricevuto sul socket viene visualizzato in un avviso popup.
case SocketActivityTriggerReason.SocketActivity:
var socket = socketInformation.StreamSocket;
DataReader reader = new DataReader(socket.InputStream);
reader.InputStreamOptions = InputStreamOptions.Partial;
await reader.LoadAsync(250);
var dataString = reader.ReadString(reader.UnconsumedBufferLength);
ShowToast(dataString);
socket.TransferOwnership(socketInformation.Id); /* Important! */
break;
- Se l'evento è stato generato perché è scaduto un timer keep-alive, il codice deve inviare alcuni dati sul socket per mantenere attivo il socket e riavviare il timer keep-alive. Anche in questo caso, è importante restituire la proprietà del socket al gestore socket per ricevere ulteriori notifiche sugli eventi:
case SocketActivityTriggerReason.KeepAliveTimerExpired:
socket = socketInformation.StreamSocket;
DataWriter writer = new DataWriter(socket.OutputStream);
writer.WriteBytes(Encoding.UTF8.GetBytes("Keep alive"));
await writer.StoreAsync();
writer.DetachStream();
writer.Dispose();
socket.TransferOwnership(socketInformation.Id); /* Important! */
break;
- Se l'evento è stato generato perché il socket è stato chiuso, ristabilire il socket, assicurandosi che, dopo aver creato il nuovo socket, se ne trasferisca la proprietà al broker di socket. In questo esempio, il nome host e la porta vengono archiviati nelle impostazioni locali in modo che possano essere usati per stabilire una nuova connessione 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;
- Non dimenticare di completare il rinvio, dopo aver completato l'elaborazione della notifica dell'evento:
deferral.Complete();
Per un esempio completo che illustra l'uso di SocketActivityTrigger e del gestore socket, vedere l'esempio SocketActivityStreamSocket. L'inizializzazione del socket viene eseguita in Scenario1_Connect.xaml.cs e l'implementazione dell'attività in background è in SocketActivityTask.cs.
Probabilmente si noterà che l'esempio chiama TransferOwnership non appena crea un nuovo socket o acquisisce un socket esistente, anziché usare il gestore OnSuspending anche per farlo, come descritto in questo argomento. Questo perché l'esempio è incentrato sulla dimostrazione di SocketActivityTrigger e non usa il socket per altre attività durante l'esecuzione. L'app sarà probabilmente più complessa e dovrebbe usare OnSuspending per determinare quando chiamare TransferOwnership.
Attivatori del canale di controllo
Assicurarsi prima di tutto di usare i trigger del canale di controllo (CCT) in modo appropriato. Se si usano connessioni DatagramSocket, StreamSocket o StreamSocketListener, è consigliabile usare SocketActivityTrigger. È possibile usare i cct per StreamSocket, ma usano più risorse e potrebbero non funzionare in modalità standby connesso.
Se si usano WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient o Windows. Web.Http.HttpClient, quindi è necessario usare ControlChannelTrigger.
ControlChannelTrigger con WebSocket
Important
La funzionalità descritta in questa sezione (ControlChannelTrigger con WebSockets) è supportata in Windows SDK versione 10.0.15063.0 e successive.
Alcune considerazioni speciali si applicano quando si usa MessageWebSocket o StreamWebSocket con ControlChannelTrigger. Esistono alcuni modelli di utilizzo specifici del trasporto e procedure consigliate da seguire quando si usa messageWebSocket o StreamWebSocket con ControlChannelTrigger. Inoltre, queste considerazioni influiscono sul modo in cui vengono gestite le richieste di ricezione di pacchetti in StreamWebSocket . Le richieste di ricezione di pacchetti in MessageWebSocket non sono interessate.
Quando si usa MessageWebSocket o StreamWebSocket con ControlChannelTrigger, è necessario seguire i modelli di utilizzo e le procedure consigliate seguenti:
- Una ricezione socket in sospeso deve essere sempre registrata. Questa operazione è necessaria per consentire l'esecuzione delle attività di notifica push.
- Il protocollo WebSocket definisce un modello standard per i messaggi keep-alive. La classe WebSocketKeepAlive può inviare messaggi keep-alive del protocollo WebSocket avviati dal client al server. La classe WebSocketKeepAlive deve essere registrata come TaskEntryPoint per un keepAliveTrigger dall'app.
Alcune considerazioni speciali influiscono sul modo in cui vengono gestite le richieste di ricezione di pacchetti in StreamWebSocket . In particolare, quando si utilizza un StreamWebSocket con il ControlChannelTrigger, l'app deve usare un modello asincrono di basso livello per gestire le operazioni di lettura anziché il modello await in C# e VB.NET o i Task in C++. Il modello asincrono non elaborato è illustrato in un esempio di codice più avanti in questa sezione.
L'uso del modello asincrono non elaborato consente Windows di sincronizzare il metodo IBackgroundTask.Run nell'attività in background per ControlChannelTrigger con la restituzione del callback di completamento della ricezione. Il metodo Run viene richiamato dopo la restituzione del callback di completamento. In questo modo si garantisce che l'app abbia ricevuto i dati o gli errori prima che venga richiamato il metodo Run .
È importante notare che l'app deve avviare un'altra operazione di lettura prima di restituire il controllo nella callback di completamento. È anche importante notare che DataReader non può essere usato direttamente con il trasporto MessageWebSocket o StreamWebSocket perché interrompe la sincronizzazione descritta in precedenza. Non è supportato l'uso del metodo DataReader.LoadAsync direttamente sopra il trasporto. Il metodo IBuffer restituito dal metodo IInputStream.ReadAsync nella proprietà StreamWebSocket.InputStream può invece essere passato successivamente al metodo DataReader.FromBuffer per un'ulteriore elaborazione.
L'esempio seguente illustra come usare un modello asincrono non elaborato per la gestione delle letture in 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);
}
}
È garantito che il gestore di completamento della lettura venga chiamato prima che venga chiamato il metodo IBackgroundTask.Run nell'attività in background di ControlChannelTrigger. Windows dispone di un meccanismo di sincronizzazione interno per attendere che un'app ritorni dal callback di completamento della lettura. L'app elabora in genere rapidamente i dati o l'errore da MessageWebSocket o da StreamWebSocket nel callback di completamento della lettura. Il messaggio stesso viene elaborato nel contesto del metodo IBackgroundTask.Run . Nell'esempio seguente, questo concetto viene illustrato utilizzando una coda di messaggi nella quale il gestore del completamento della lettura inserisce il messaggio e l'attività in background lo elabora successivamente.
L'esempio seguente mostra il gestore di completamento di lettura da usare con un modello asincrono non elaborato per la gestione delle letture in 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 dettaglio aggiuntivo per Websocket è il gestore keep-alive. Il protocollo WebSocket definisce un modello standard per i messaggi keep-alive.
Quando si usa MessageWebSocket o StreamWebSocket, registrare un'istanza della classe WebSocketKeepAlive come TaskEntryPoint per un KeepAliveTrigger per consentire all'app di annullare la sospensione e inviare periodicamente messaggi keep-alive al server (endpoint remoto). Questa operazione deve essere eseguita sia nel codice dell'app per la registrazione in background sia nel manifest del pacchetto.
Punto di ingresso dell'attività di Windows. Sockets.WebSocketKeepAlive deve essere specificato in due posizioni:
- Quando si crea un trigger KeepAliveTrigger nel codice sorgente (vedere l'esempio seguente).
- Nel manifest del pacchetto dell'app per la dichiarazione dell'attività in background KeepAlive.
L'esempio seguente aggiunge una notifica di attivazione di rete e un trigger keep-alive nell'elemento <Application> di un manifest dell'app.
<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>
Un'app deve essere estremamente attenta quando si usa un'istruzione await nel contesto di controlChannelTrigger e un'operazione asincrona in un oggetto StreamWebSocket, MessageWebSocket o StreamSocket. Un oggetto Task<bool> può essere usato per registrare un ControlChannelTrigger per le notifiche push e i keep-alive WebSocket su StreamWebSocket e per stabilire la connessione di trasporto. Durante la registrazione, StreamWebSocket è impostato come meccanismo di trasporto per ControlChannelTrigger e viene avviata un'operazione di lettura. Task.Result blocca il thread corrente fino a quando tutti i passaggi dell'attività non eseguono e restituiscono istruzioni nel corpo del messaggio. L'attività non viene risolta finché il metodo non restituisce true o false. Ciò garantisce che venga eseguito l'intero metodo. Il Task può contenere più istruzioni await racchiuse nel Task. Questo modello deve essere usato con l'oggetto ControlChannelTrigger quando un oggetto StreamWebSocket o MessageWebSocket viene usato come trasporto. Per le operazioni che potrebbero richiedere un lungo periodo di tempo (ad esempio, un'operazione di lettura asincrona tipica), l'app deve usare il modello asincrono non elaborato descritto in precedenza.
L'esempio seguente registra ControlChannelTrigger per gestire le notifiche push e i keep-alive WebSocket su 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;
}
Per altre informazioni sull'uso di MessageWebSocket o StreamWebSocket con ControlChannelTrigger, vedere l'esempio ControlChannelTrigger StreamWebSocket.
ControlChannelTrigger con HttpClient
Alcune considerazioni speciali si applicano quando si usa HttpClient con ControlChannelTrigger. Esistono alcuni modelli di utilizzo specifici del trasporto e procedure consigliate da seguire quando si usa httpClient con ControlChannelTrigger. Inoltre, queste considerazioni influiscono sul modo in cui vengono gestite le richieste di ricezione di pacchetti in HttpClient .
NotaHttpClient che utilizza SSL non è attualmente supportato con la funzionalità di attivazione di rete e ControlChannelTrigger. Quando si usa HttpClient con ControlChannelTrigger, è necessario seguire i modelli di utilizzo e le procedure consigliate seguenti:
- L'app potrebbe dover impostare varie proprietà e intestazioni nell'oggetto HttpClient o HttpClientHandler nello spazio dei nomi System.Net.Http prima di inviare la richiesta all'URI specifico.
- Un'app potrebbe dover effettuare una richiesta iniziale per testare e configurare correttamente il trasporto prima di creare il trasporto HttpClient da usare con ControlChannelTrigger. Dopo che l'app determina che il trasporto può essere configurato correttamente, un oggetto HttpClient può essere configurato come oggetto di trasporto usato con l'oggetto ControlChannelTrigger . Questo processo è progettato per impedire ad alcuni scenari di interrompere la connessione stabilita nel trasporto. Se si usa SSL con un certificato, un'app potrebbe richiedere la visualizzazione di una finestra di dialogo per l'immissione del PIN o se sono disponibili più certificati tra cui scegliere. Potrebbe essere necessaria l'autenticazione proxy e l'autenticazione server. Se l'autenticazione proxy o server scade, la connessione potrebbe essere chiusa. Un modo in cui un'app può gestire questi problemi di scadenza dell'autenticazione consiste nell'impostare un timer. Quando è necessario un reindirizzamento HTTP, non è garantito che la seconda connessione possa essere stabilita in modo affidabile. Una richiesta di test iniziale garantisce che l'app possa usare l'URL di reindirizzamento più aggiornato prima di utilizzare l'oggetto HttpClient come trasporto con l'oggetto ControlChannelTrigger.
A differenza di altri trasporti di rete, l'oggetto HttpClient non può essere passato direttamente al metodo UsingTransport dell'oggetto ControlChannelTrigger . Al contrario, un oggetto HttpRequestMessage deve essere appositamente costruito per l'uso con l'oggetto HttpClient e ControlChannelTrigger. L'oggetto HttpRequestMessage viene creato usando il metodo RtcRequestFactory.Create . L'oggetto HttpRequestMessage creato viene quindi passato al metodo UsingTransport .
L'esempio seguente illustra come costruire un oggetto HttpRequestMessage da usare con l'oggetto HttpClient e 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;
}
}
Alcune considerazioni speciali influiscono sul modo in cui vengono gestite le richieste di invio di richieste HTTP su HttpClient per avviare la ricezione di una risposta. In particolare, quando si usa un httpClient con ControlChannelTrigger, l'app deve usare un'attività per gestire gli invii anziché il modello await .
Usando HttpClient, non esiste alcuna sincronizzazione con il metodo IBackgroundTask.Run nell'attività in background per ControlChannelTrigger con la restituzione del callback di completamento della ricezione. Per questo motivo, l'app può usare solo la tecnica httpResponseMessage di blocco nel metodo Run e attendere che l'intera risposta venga ricevuta.
L'uso di HttpClient con ControlChannelTrigger è notevolmente diverso dai trasporti StreamSocket, MessageWebSocket o StreamWebSocket . Il callback di ricezione di HttpClient viene fornito all'app tramite un Task dal codice di HttpClient. Ciò significa che l'attività di notifica push ControlChannelTrigger verrà attivata non appena i dati o gli errori vengono inviati all'app. Nell'esempio seguente, il codice archivia responseTask, restituito dal metodo HttpClient.SendAsync, in un archivio globale che l'attività di notifica push recupererà e elaborerà inline.
L'esempio seguente illustra come gestire le richieste di invio su HttpClient quando viene usato con 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'esempio seguente illustra come leggere le risposte ricevute in HttpClient quando viene usato con 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;
}
Per altre informazioni sull'uso di HttpClient con ControlChannelTrigger, vedere l'esempio HttpClient ControlChannelTrigger.
ControlChannelTrigger con IXMLHttpRequest2
Alcune considerazioni speciali si applicano quando si usa IXMLHTTPRequest2 con ControlChannelTrigger. Esistono alcuni modelli di utilizzo specifici del trasporto e procedure consigliate da seguire quando si usa un IXMLHTTPRequest2 con ControlChannelTrigger. L'uso di ControlChannelTrigger non influisce sul modo in cui vengono gestite le richieste di invio o ricezione di richieste HTTP in IXMLHTTPRequest2 .
Modelli di utilizzo e procedure consigliate quando si usa IXMLHTTPRequest2 con ControlChannelTrigger
- Un oggetto IXMLHTTPRequest2 se utilizzato come trasporto ha una durata di una sola richiesta/risposta. Quando viene usato con l'oggetto ControlChannelTrigger , è utile creare e configurare l'oggetto ControlChannelTrigger una sola volta e quindi chiamare ripetutamente il metodo UsingTransport , ogni volta che si associa un nuovo oggetto IXMLHTTPRequest2 . Un'app deve eliminare l'oggetto IXMLHTTPRequest2 precedente prima di fornire un nuovo oggetto IXMLHTTPRequest2 per assicurarsi che l'app non superi i limiti di risorse allocati.
- L'app potrebbe dover chiamare i metodi SetProperty e SetRequestHeader per configurare il trasporto HTTP prima di chiamare il metodo Send .
- Un'app potrebbe dover effettuare una richiesta send iniziale per testare e configurare correttamente il trasporto prima di creare il trasporto da usare con ControlChannelTrigger. Dopo che l'app determina che il trasporto è configurato correttamente, l'oggetto IXMLHTTPRequest2 può essere configurato come oggetto di trasporto usato con ControlChannelTrigger. Questo processo è progettato per impedire ad alcuni scenari di interrompere la connessione stabilita nel trasporto. Se si usa SSL con un certificato, un'app potrebbe richiedere la visualizzazione di una finestra di dialogo per l'immissione del PIN o se sono disponibili più certificati tra cui scegliere. Potrebbe essere necessaria l'autenticazione proxy e l'autenticazione server. Se l'autenticazione proxy o server scade, la connessione potrebbe essere chiusa. Un modo in cui un'app può gestire questi problemi di scadenza dell'autenticazione consiste nell'impostare un timer. Quando è necessario un reindirizzamento HTTP, non è garantito che la seconda connessione possa essere stabilita in modo affidabile. Una richiesta di test iniziale garantirà che l'app possa usare l'URL reindirizzato più aggiornato prima di usare l'oggetto IXMLHTTPRequest2 come meccanismo di trasporto con l'oggetto ControlChannelTrigger.
Per altre informazioni sull'uso di IXMLHTTPRequest2 con ControlChannelTrigger, vedere l'esempio ControlChannelTrigger con IXMLHTTPRequest2.