Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Чтобы продолжить сетевое взаимодействие, пока приложение не находится на переднем плане, ваше приложение может использовать фоновые задачи и один из двух следующих вариантов.
- Брокер сокетов. Если приложение использует сокеты для долгосрочных подключений, то при выходе из переднего плана он может делегировать владение сокетом брокеру системного сокета. Затем брокер: активирует приложение при поступлении трафика на сокет; передает права владения обратно в приложение; и ваше приложение обрабатывает поступающий трафик.
- Триггеры канала управления.
Выполнение сетевых операций в фоновых задачах
- Используйте 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 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();
}
В обработчике событий фоновой задачи:
- Сначала получите отсрочку фоновой задачи, чтобы можно было обрабатывать событие с помощью асинхронных методов.
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;
- Если событие было вызвано истечением таймера keep-alive, то ваш код должен отправить какие-либо данные через сокет, чтобы поддерживать сокет активным и перезапустить таймер keep-alive. Опять же, важно вернуть владение сокетом обратно брокеру сокетов, чтобы получить дальнейшие уведомления о событиях:
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.HttpClient, а затем необходимо использовать ControlChannelTrigger.
ControlChannelTrigger с использованием WebSockets
Important
Функция, описанная в этом разделе (ControlChannelTrigger с WebSockets), поддерживается в пакете SDK Windows версии 10.0.15063.0 и более поздних версий.
Некоторые особые аспекты применяются при использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger. При использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger следует соблюдать некоторые характерные для транспорта сценарии использования и рекомендации по оптимальной работе. Кроме того, эти рекомендации влияют на способ обработки запросов на получение пакетов в StreamWebSocket . Запросы на получение пакетов в MessageWebSocket не затрагиваются.
При использовании MessageWebSocket или StreamWebSocket с ControlChannelTrigger следует соблюдать следующие шаблоны использования и рекомендации.
- Выдающийся сокет должен быть размещен в любое время. Это необходимо, чтобы задачи push-уведомлений выполнялись.
- Протокол WebSocket определяет стандартную модель для сообщений в режиме поддержания активности. Класс WebSocketKeepAlive может отправлять на сервер сообщения поддержки активности протокола WebSocket, инициируемые клиентом. Класс WebSocketKeepAlive должен быть зарегистрирован как TaskEntryPoint для KeepAliveTrigger приложением.
Некоторые особые соображения влияют на то, как обрабатываются запросы на получение пакетов в StreamWebSocket . В частности, при использовании StreamWebSocket с ControlChannelTrigger приложение должно использовать необработанный асинхронный шаблон для обработки операций чтения вместо модели ожидания в C# и VB.NET или Tasks в C++. Необработанный асинхронный шаблон показан в примере кода далее в этом разделе.
Использование необработанного асинхронного шаблона позволяет Windows синхронизировать метод IBackgroundTask.Run в фоновой задаче для ControlChannelTrigger с возвратом обратного вызова завершения получения. Метод Run вызывается после того, как обратный вызов завершения вернётся. Это гарантирует, что приложение получило данные и ошибки перед вызовом метода Run .
Важно отметить, что приложение должно выполнить еще одну операцию чтения, прежде чем управление будет возвращено из обработчика завершения. Важно также отметить, что DataReader нельзя напрямую использовать с транспортом MessageWebSocket или StreamWebSocket , так как это нарушает синхронизацию, описанную выше. Использование метода DataReader.LoadAsync непосредственно поверх транспортного уровня не поддерживается. Вместо этого IBuffer, возвращаемый методом 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);
}
Ещё одна важная деталь в случае WebSocket — обработчик keep-alive. Протокол WebSocket определяет стандартную модель для сообщений в режиме поддержания активности.
При использовании MessageWebSocket или StreamWebSocket зарегистрируйте экземпляр класса WebSocketKeepAlive в качестве TaskEntryPoint для триггера KeepAliveTrigger, чтобы приложение можно было вывести из приостановленного состояния и чтобы оно могло периодически отправлять keep-alive-сообщения на сервер (удаленную конечную точку). Это необходимо сделать как часть кода фоновой регистрации приложения, а также в манифесте пакета.
Эта точка входа задачи 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<bool> можно использовать для регистрации 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 будет запускаться сразу после отправки данных или ошибок в приложение. В приведённом ниже примере код сохраняет объект `responseTask`, возвращаемый методом 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 .
- Приложению может потребоваться выполнить первоначальный запрос Send, чтобы проверить и правильно настроить транспорт перед созданием транспорта для использования с ControlChannelTrigger. После правильной настройки транспорта объект IXMLHTTPRequest2 можно настроить в качестве транспортного объекта, используемого с ControlChannelTrigger. Этот процесс предназначен для предотвращения некоторых сценариев, которые могут нарушить соединение, установленное через транспорт. При использовании SSL с сертификатом приложению может потребоваться отображение диалогового окна для ввода PIN-кода или если доступно несколько сертификатов для выбора. Может потребоваться проверка подлинности прокси-сервера и проверка подлинности сервера. Если срок действия прокси-сервера или проверки подлинности сервера истекает, подключение может быть закрыто. Одним из способов решения этих проблем с истечением срока действия проверки подлинности является установка таймера. Если требуется перенаправление HTTP, это не гарантирует надежное создание второго подключения. Начальный тестовый запрос гарантирует, что приложение сможет использовать самый актуальный перенаправленный URL-адрес перед использованием объекта IXMLHTTPRequest2 в качестве транспорта с объектом ControlChannelTrigger.
Дополнительные сведения об использовании IXMLHTTPRequest2 с ControlChannelTrigger см. в примере ControlChannelTrigger with IXMLHTTPRequest2.
Важные API
Windows developer