Передача данных по сети в фоновом режиме
Чтобы продолжить сетевое взаимодействие, пока оно не на переднем плане, приложение может использовать фоновые задачи и один из таких двух вариантов.
- Брокер сокетов. Если ваши приложения используют сокеты для продолжительных соединений, они могут делегировать право собственности системному брокеру сокетов, когда покидают передний план. Затем брокер активирует приложение, когда трафик поступает в сокет, передает право собственности приложению, и приложение обрабатывает входящий трафик.
- Триггеры канала управления.
Выполнение сетевых операций в фоновых задачах
- Используйте SocketActivityTrigger, чтобы активировать фоновую задачу при получении пакета и возникновении необходимости выполнить кратковременную задачу. После выполнения задачи фоновая задача должна завершить работу для экономии энергии.
- Используйте ControlChannelTrigger, чтобы активировать фоновую задачу при получении пакета и возникновении необходимости выполнить долговременную задачу.
Условия и флаги, связанные с сетью
- Добавьте условие InternetAvailable в фоновую задачу BackgroundTaskBuilder.AddCondition, чтобы задержать активацию фоновой задачи до запуска сетевого стека. Это условие экономит энергию, так как фоновая задача не будет выполняться, пока сеть недоступна. Это условие не поддерживает активацию в режиме реального времени.
Независимо от используемого триггера задайте условие IsNetworkRequested для фоновой задачи, чтобы обеспечить сохранность подключения к сети, пока выполняется фоновая задача. Это указывает инфраструктуре фоновых задач на необходимость поддержания соединения во время выполнения задачи, даже если устройство переходит в режим ожидания с подключением. Если ваша фоновая задача не использует IsNetworkRequested, она не сможет получить доступ к сети в режиме ожидания с подключением (например, при выключении экрана телефона).
Брокер сокетов и SocketActivityTrigger
Если приложение использует подключения DatagramSocket, StreamSocket или StreamSocketListener, то следует использовать SocketActivityTrigger и брокер сокетов, чтобы получать уведомления о поступлении трафика для приложения, пока он не находится на переднем плане.
Чтобы приложение получило и обработает данные, полученные в сокете, если приложение не активно, приложение должно выполнить однократную настройку при запуске, а затем передать владение сокетом брокеру сокетов при переходе в состояние, в котором он не активен.
Этапы однократной настройки — создание триггера, регистрация фоновой задачи триггера и включение сокета для брокера сокетов:
- Создайте SocketActivityTrigger и зарегистрируйте фоновую задачу для триггера с параметром TaskEntryPoint, заданным в коде для обработки полученного пакета.
var socketTaskBuilder = new BackgroundTaskBuilder();
socketTaskBuilder.Name = _backgroundTaskName;
socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
var trigger = new SocketActivityTrigger();
socketTaskBuilder.SetTrigger(trigger);
_task = socketTaskBuilder.Register();
- Вызов 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");
После правильной настройки сокета, когда приложение будет приостановлено, вызовите TransferOwnership на сокете, чтобы передать его брокеру сокета. Брокер отслеживает сокет и активирует фоновую задачу при получении данных. В следующем примере содержится функция TransferOwnership служебной программы для передачи сокетов StreamSocketListener. (Обратите внимание, что разные типы сокетов имеют собственный метод TransferOwnership, поэтому необходимо вызвать метод, подходящий для сокета, собственность для которого передается. Код, вероятно, будет содержать перегруженное вспомогательное приложение TransferOwnership с одной реализацией для каждого используемого типа сокета, чтобы код для OnSuspending оставался понятным для чтения.)
Приложение передает владение сокетом брокеру сокета и передает идентификатор фоновой задачи с помощью соответствующего одного из следующих методов:
- Один из методов TransferOwnership в DatagramSocket.
- Один из методов TransferOwnership в StreamSocket.
- Один из методов TransferOwnership в StreamSocketListener.
// 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();
}
В обработчике событий фоновой задачи:
- Сначала получите отсрочку фоновой задачи, чтобы можно было обрабатывать событие с помощью асинхронных методов.
var deferral = taskInstance.GetDeferral();
- Затем извлеките socketActivityTriggerDetails из аргументов событий и найдите причину, по которой возникло событие:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
var socketInformation = details.SocketInformation;
switch (details.Reason)
- Если событие возникло из-за действия сокета, создайте DataReader в сокете, загрузите средство чтения асинхронно, а затем используйте данные в соответствии с дизайном приложения. Обратите внимание, что необходимо вернуть владение сокетом обратно брокеру сокетов, чтобы получить уведомление о дальнейшем действии сокета.
В следующем примере текст, полученный на сокете, отображается в всплывающем элементе.
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;
- Если событие было поднято из-за истечения срока действия таймера хранения, код должен отправлять некоторые данные по сокету, чтобы сохранить сокет в живых и перезапустить таймер хранения. Опять же, важно вернуть владение сокетом обратно брокеру сокетов, чтобы получить дальнейшие уведомления о событиях:
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;
- Если событие было поднято, так как сокет был закрыт, повторно установите сокет, убедившись, что после создания нового сокета вы передаете его в брокер сокетов. В этом примере имя узла и порт хранятся в локальных параметрах, чтобы их можно было использовать для установления нового подключения к сокету:
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;
- Не забудьте завершить отсрочку после завершения обработки уведомления о событии:
deferral.Complete();
Полный пример демонстрации использования SocketActivityTrigger и брокера сокетов см. в примере SocketActivityStreamSocket. Инициализация сокета выполняется в Scenario1_Connect.xaml.cs, а реализация фоновой задачи — в SocketActivityTask.cs.
Вероятно, вы заметите, что пример вызывает TransferOwnership сразу после создания нового сокета или получения существующего сокета, а не с помощью обработчика OnSuspending даже для этого, как описано в этом разделе. Это связано с тем, что пример фокусируется на демонстрации SocketActivityTrigger и не использует сокет для других действий во время его выполнения. Ваше приложение, вероятно, будет более сложным и должно использовать OnSuspending , чтобы определить, когда следует вызывать TransferOwnership.
Триггеры канала управления
Во-первых, убедитесь, что вы используете триггеры канала управления (CCT) соответствующим образом. Если вы используете соединения DatagramSocket, StreamSocket или StreamSocketListener, мы рекомендуем использовать SocketActivityTrigger. Вы можете использовать CCT для StreamSocket, но они используют больше ресурсов и могут не работать в режиме ожидания подключения.
Если вы используете WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient или Windows.Web.Http.HttpClient, вы должны использовать ControlChannelTrigger.
ControlChannelTrigger с WebSockets
Внимание
Функция, описанная в этом разделе (ControlChannelTrigger с WebSockets) поддерживается в версии пакета SDK 10.0.15063.0 или более ранней. Она также поддерживается в предварительных версиях Windows 10 Insider Preview.
Некоторые особые аспекты применяются при использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger. При использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger следует учитывать некоторые шаблоны использования транспорта и рекомендации. Кроме того, эти рекомендации влияют на способ обработки запросов на получение пакетов в StreamWebSocket . Запросы на получение пакетов в MessageWebSocket не затрагиваются.
При использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger следует соблюдать следующие шаблоны использования и рекомендации.
- Выдающийся сокет должен быть размещен в любое время. Это необходимо, чтобы задачи push-уведомлений выполнялись.
- Протокол WebSocket определяет стандартную модель для сообщений в режиме поддержания активности. Класс WebSocketKeepAlive может отправлять сообщения, инициированные клиентом WebSocket, на сервер. Класс WebSocketKeepAlive должен быть зарегистрирован как TaskEntryPoint для KeepAliveTrigger приложением.
Некоторые особые соображения влияют на то, как обрабатываются запросы на получение пакетов в StreamWebSocket. В частности, при использовании StreamWebSocket с ControlChannelTrigger приложение должно использовать необработанный асинхронный шаблон для обработки операций чтения вместо модели ожидания в C# и VB.NET или задач в C++. Необработанный асинхронный шаблон показан в примере кода далее в этом разделе.
Использование необработанного асинхронного шаблона позволяет Windows синхронизировать метод IBackgroundTask.Run в фоновой задаче для ControlChannelTrigger с возвратом обратного вызова завершения получения. Метод Run вызывается после возврата обратного вызова завершения. Это гарантирует, что приложение получило данные и ошибки перед вызовом метода Run .
Важно отметить, что приложению необходимо опубликовать еще одно чтение, прежде чем он возвращает элемент управления из обратного вызова завершения. Важно также отметить, что DataReader нельзя использовать непосредственно с транспортом MessageWebSocket или StreamWebSocket, так как это нарушает синхронизацию, описанную выше. Он не поддерживается для использования метода DataReader.LoadAsync непосредственно на вершине транспорта. Вместо этого метод IInputStream.ReadAsync в свойстве StreamWebSocket.InputStream можно позже передать методу DataReader.FromBuffer для дальнейшей обработки.
В следующем примере показано, как использовать необработанный асинхронный шаблон для обработки операций чтения в 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);
}
}
Обработчик завершения чтения гарантированно запускается до вызова метода IBackgroundTask.Run в фоновой задаче элемента ControlChannelTrigger. Windows имеет внутреннюю синхронизацию, чтобы ожидать возвращения приложения из обратного вызова завершения чтения. Приложение обычно быстро обрабатывает данные или ошибку из MessageWebSocket или StreamWebSocket в обратном вызове завершения чтения. Само сообщение обрабатывается в контексте метода IBackgroundTask.Run . В этом примере ниже показана эта точка с помощью очереди сообщений, в которую обработчик завершения чтения вставляет сообщение и фоновую задачу более поздних процессов.
В следующем примере показан обработчик завершения чтения, используемый с необработанным асинхронным шаблоном для обработки операций чтения в 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);
}
Дополнительные сведения для Websockets — это обработчик сохраняемой активности. Протокол WebSocket определяет стандартную модель для сообщений в режиме поддержания активности.
При использовании MessageWebSocket или StreamWebSocket зарегистрируйте экземпляр класса WebSocketKeepAlive как TaskEntryPoint для KeepAliveTrigger, чтобы приложение было возобновлено и периодически отправляло на сервер (на удаленную конечную точку) сообщения проверки активности. Это необходимо сделать как часть кода фоновой регистрации приложения, а также в манифесте пакета.
Эта точка входа задачи Windows.Sockets.WebSocketKeepAlive должна быть указана в двух местах:
- При создании триггера KeepAliveTrigger в исходном коде (см. пример ниже).
- В манифесте пакета приложения для объявления фоновой задачи хранения.
В следующем примере добавляется уведомление о сетевом триггере и хранимый триггер в <элементе Application> в манифесте приложения.
<Extensions>
<Extension Category="windows.backgroundTasks"
Executable="$targetnametoken$.exe"
EntryPoint="Background.PushNotifyTask">
<BackgroundTasks>
<Task Type="controlChannel" />
</BackgroundTasks>
</Extension>
<Extension Category="windows.backgroundTasks"
Executable="$targetnametoken$.exe"
EntryPoint="Windows.Networking.Sockets.WebSocketKeepAlive">
<BackgroundTasks>
<Task Type="controlChannel" />
</BackgroundTasks>
</Extension>
</Extensions>
Приложение должно быть очень осторожным при использовании инструкции await в контексте ControlChannelTrigger и асинхронной операции в StreamWebSocket, MessageWebSocket или StreamSocket. Логический объект Task>< можно использовать для регистрации элемента ControlChannelTrigger для push-уведомлений и сохранения активности WebSocket в StreamWebSocket и подключения транспорта. В рамках регистрации транспорт StreamWebSocket задается в качестве транспорта для ControlChannelTrigger и публикуется чтение. Task.Result блокирует текущий поток до тех пор, пока все шаги в задаче не выполняются и возвращают инструкции в тексте сообщения. Задача не разрешается, пока метод не возвращает значение true или false. Это гарантирует выполнение всего метода. Задача может содержать несколько инструкций await, защищенных задачей. Этот шаблон следует использовать с объектом ControlChannelTrigger, если в качестве транспорта используется StreamWebSocket или MessageWebSocket. Для таких операций, которые могут занять длительный период времени (обычной асинхронной операции чтения, например), приложение должно использовать необработанный асинхронный шаблон, рассмотренный ранее.
Следующий пример регистрирует ControlChannelTrigger для push-уведомлений и webSocket в 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
}
Дополнительные сведения об использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger см. в примере ControlChannelTrigger StreamWebSocket.
ControlChannelTrigger с HttpClient
Некоторые особые рекомендации применяются при использовании HttpClient с ControlChannelTrigger. Существуют некоторые шаблоны использования транспорта и рекомендации, которые следует соблюдать при использовании HttpClient с ControlChannelTrigger. Кроме того, эти рекомендации влияют на то, как обрабатываются запросы на получение пакетов в HttpClient .
ПримечаниеHttpClient с использованием SSL в настоящее время не поддерживается с помощью функции триггера сети и ControlChannelTrigger. При использовании HttpClient с ControlChannelTrigger следует следовать следующим шаблонам использования и рекомендациям.
- Приложению может потребоваться задать различные свойства и заголовки объекта HttpClient или HttpClientHandler в пространстве имен System.Net.Http перед отправкой запроса в конкретный универсальный код ресурса (URI).
- Приложению может потребоваться выполнить первоначальный запрос для тестирования и настройки транспорта перед созданием транспорта HttpClient, который будет использоваться с ControlChannelTrigger. После того как приложение определит, что транспорт может быть правильно настроен, объект HttpClient можно настроить в качестве транспортного объекта, используемого с объектом ControlChannelTrigger . Этот процесс предназначен для предотвращения нарушения соединения, установленного по транспорту. Использование SSL с сертификатом, приложению может потребоваться диалоговое окно для записи ПИН-кода или наличие нескольких сертификатов для выбора. Может потребоваться проверка подлинности прокси-сервера и проверка подлинности сервера. Если срок действия прокси-сервера или проверки подлинности сервера истекает, подключение может быть закрыто. Одним из способов решения этих проблем с истечением срока действия проверки подлинности является установка таймера. Если требуется перенаправление HTTP, это не гарантирует надежное создание второго подключения. Первоначальный тестовый запрос гарантирует, что приложение может использовать самый актуальный URL-адрес перенаправления перед использованием объекта HttpClient в качестве транспорта с объектом ControlChannelTrigger .
В отличие от других сетевых транспортов, объект HttpClient нельзя передать непосредственно в метод UsingTransport объекта ControlChannelTrigger. Вместо этого объект HttpRequestMessage должен быть специально создан для использования с объектом HttpClient и ControlChannelTrigger. Объект HttpRequestMessage создается с помощью метода RtcRequestFactory.Create. Созданный объект HttpRequestMessage затем передается в метод UsingTransport.
В следующем примере показано, как создать объект HttpRequestMessage для использования с объектом HttpClient и 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;
}
}
Некоторые специальные рекомендации влияют на то, как запросы на отправку HTTP-запросов на HttpClient для инициирования получения ответа обрабатываются. В частности, при использовании HttpClient с ControlChannelTrigger приложение должно использовать задачу для обработки отправки вместо модели ожидания.
Использование HttpClient не выполняет синхронизацию с методом IBackgroundTask.Run в фоновой задаче для ControlChannelTrigger с возвратом обратного вызова завершения получения. По этой причине приложение может использовать только блокирующий метод HttpResponseMessage в методе Run и ждать, пока не будет получен весь ответ.
Использование HttpClient с ControlChannelTrigger заметно отличается от транспорта StreamSocket, MessageWebSocket или StreamWebSocket. Обратный вызов HttpClient передается через задачу приложению, так как код HttpClient . Это означает, что задача push-уведомлений ControlChannelTrigger будет запускаться сразу после отправки данных или ошибок в приложение. В приведенном ниже примере код сохраняет ответTask, возвращаемый методом HttpClient.SendAsync , в глобальное хранилище, что задача push-уведомления будет собирать и обрабатывать встроенные данные.
В следующем примере показано, как обрабатывать запросы на отправку httpClient при использовании с 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);
}
В следующем примере показано, как считывать ответы, полученные на httpClient при использовании с 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;
}
Дополнительные сведения об использовании HttpClient с ControlChannelTrigger см. в примере HttpClient ControlChannelTrigger.
ControlChannelTrigger с IXMLHttpRequest2
Некоторые особые аспекты применяются при использовании IXMLHTTPRequest2 с ControlChannelTrigger. При использовании IXMLHTTPRequest2 с ControlChannelTrigger следует соблюдать некоторые шаблоны использования для конкретного транспорта и рекомендации. Использование ControlChannelTrigger не влияет на то, как обрабатываются запросы на отправку или получение HTTP-запросов на IXMLHTTPRequest2 .
Шаблоны использования и рекомендации при использовании IXMLHTTPRequest2 с ControlChannelTrigger
- Объект IXMLHTTPRequest2 при использовании в качестве транспорта имеет время существования только одного запроса или ответа. При использовании с объектом ControlChannelTrigger удобно создавать и настраивать объект ControlChannelTrigger один раз, а затем многократно вызывать метод UsingTransport при каждом связывании нового объекта IXMLHTTPRequest2. Приложение должно удалить предыдущий объект IXMLHTTPRequest2 перед предоставлением нового объекта IXMLHTTPRequest2, чтобы убедиться, что приложение не превышает выделенные ограничения ресурсов.
- Приложению может потребоваться вызвать метод SetProperty и SetRequestHeader, чтобы настроить транспорт HTTP перед вызовом метода Send.
- Приложению может потребоваться выполнить первоначальный запрос отправки для тестирования и правильной настройки транспорта перед созданием транспорта, который будет использоваться с ControlChannelTrigger. После правильной настройки транспорта объект IXMLHTTPRequest2 можно настроить в качестве транспортного объекта, используемого с ControlChannelTrigger. Этот процесс предназначен для предотвращения нарушения соединения, установленного по транспорту. Использование SSL с сертификатом, приложению может потребоваться диалоговое окно для записи ПИН-кода или наличие нескольких сертификатов для выбора. Может потребоваться проверка подлинности прокси-сервера и проверка подлинности сервера. Если срок действия прокси-сервера или проверки подлинности сервера истекает, подключение может быть закрыто. Одним из способов решения этих проблем с истечением срока действия проверки подлинности является установка таймера. Если требуется перенаправление HTTP, это не гарантирует надежное создание второго подключения. Первоначальный запрос теста гарантирует, что приложение может использовать самый актуальный URL-адрес перенаправленного перед использованием объекта IXMLHTTPRequest2 в качестве транспорта с объектом ControlChannelTrigger .
Дополнительные сведения об использовании IXMLHTTPRequest2 с ControlChannelTrigger см. в примере ControlChannelTrigger с примером IXMLHTTPRequest2.