若要在不在前台时继续网络通信,应用可以使用后台任务和这两个选项之一。
- 套接字代理。 如果你的应用将套接字用于长期连接,则当它离开前台时,它可以将套接字的所有权委托给系统套接字代理。 在流量到达套接字上后,该代理会激活应用并将所有权传输回应用;然后应用会处理到达的流量。
- 控制通道触发器。
在后台任务中执行网络操作
- 在检索到程序包并且需要执行生存期较短的任务时,使用 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 代码仍然易于阅读。
通过使用以下一种适当方法,应用会将套接字的所有权传输给套接字代理并传递后台任务的 ID:
- DatagramSocket 上的 TransferOwnership 方法之一。
- StreamSocket 上的 TransferOwnership 方法之一。
- StreamSocketListener 上的 TransferOwnership 方法之一。
// 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,异步加载读取器,然后根据应用的设计使用数据。 请注意,必须将套接字的所有权返回到套接字代理,才能再次收到后续套接字活动的通知。
在以下示例中,在套接字上接收的文本显示在 Toast 中。
case SocketActivityTriggerReason.SocketActivity:
var socket = socketInformation.StreamSocket;
DataReader reader = new DataReader(socket.InputStream);
reader.InputStreamOptions = InputStreamOptions.Partial;
await reader.LoadAsync(250);
var dataString = reader.ReadString(reader.UnconsumedBufferLength);
ShowToast(dataString);
socket.TransferOwnership(socketInformation.Id); /* Important! */
break;
- 如果由于保持活动计时器过期而引发该事件,则代码应通过套接字发送一些数据,以使套接字保持活动状态并重启保持活动计时器。 同样,请务必将套接字的所有权返回给套接字代理,以便接收进一步的事件通知:
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,但它们使用更多资源,在连接待机模式下可能不起作用。
如果使用 WebSocket、IXMLHTTPRequest2、System.Net.Http.HttpClient 或 Windows。Web.Http.HttpClient,则必须使用 ControlChannelTrigger。
带有 WebSocket 的 ControlChannelTrigger
Important
Windows SDK 版本 10.0.15063.0 及更高版本中支持本节中所述的功能(使用 WebSocket 的 ControlChannelTrigger)。
将 MessageWebSocket 或 StreamWebSocket 与 ControlChannelTrigger 配合使用时,需要注意一些特殊注意事项。 在将 MessageWebSocket 或 StreamWebSocket 与 ControlChannelTrigger 配合使用时,应遵循一些特定于传输的使用模式和最佳做法。 此外,这些注意事项会影响处理 StreamWebSocket 上接收数据包的请求的方式。 在 MessageWebSocket 上接收数据包的请求不会受到影响。
将 MessageWebSocket 或 StreamWebSocket 与 ControlChannelTrigger 配合使用时,应遵循以下使用模式和最佳做法:
- 必须始终保持发布一个未完成的套接字接收。 这是允许推送通知任务发生所必需的。
- WebSocket 协议为保活消息定义了一种标准模型。 WebSocketKeepAlive 类可以将客户端启动的 WebSocket 协议保持活动消息发送到服务器。 WebSocketKeepAlive 类应由应用注册为 KeepAliveTrigger 的 TaskEntryPoint。
一些特殊注意事项会影响处理 StreamWebSocket 上接收数据包的请求的方式。 具体而言,将 StreamWebSocket 与 ControlChannelTrigger 一起使用时,应用必须使用原始异步模式来处理读取操作,而不能使用 C# 和 VB.NET 中的 await 模型或 C++ 中的任务。 本部分后面的代码示例演示了原始异步模式。
使用原始异步模式可使 Windows 将针对 ControlChannelTrigger 的后台任务上的 IBackgroundTask.Run 方法与接收完成回调的返回进行同步。 完成回调返回后调用 Run 方法。 这可确保在调用 Run 方法之前,应用已收到数据/错误。
请务必注意,应用必须在从完成回调返回控件前发布另一个读取操作。 另请注意, DataReader 不能直接与 MessageWebSocket 或 StreamWebSocket 传输一起使用,因为这会中断上述同步。 不支持直接在传输顶部使用 DataReader.LoadAsync 方法。 相反,StreamWebSocket.InputStream 属性上的 IInputStream.ReadAsync 方法返回的 IBuffer 稍后可以传递给 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);
}
}
在调用 ControlChannelTrigger 的后台任务上的 IBackgroundTask.Run 方法前,将保证引发读取完成处理程序。 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,还有一个细节是保活处理程序。 WebSocket 协议定义用于保持活动消息的标准模型。
使用 MessageWebSocket 或 StreamWebSocket 时,将 WebSocketKeepAlive 类实例注册为 KeepAliveTrigger 的 TaskEntryPoint ,以允许应用取消暂停,并定期将 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>
应用在 ControlChannelTrigger 的上下文中使用 await 语句,以及对 StreamWebSocket、MessageWebSocket 或 StreamSocket 执行异步操作时,必须格外小心。 可以使用 Task<bool> 对象为 StreamWebSocket 上的推动通知和 WebSocket 保持连接注册 ControlChannelTrigger,然后连接传输。 作为注册的一部分,将 StreamWebSocket 传输设置为 ControlChannelTrigger 的传输,并发布读取。 Task.Result 将阻止当前线程,直到任务中的所有步骤在消息正文中执行并返回语句。 只有当该方法返回 true 或 false 时,任务才会得到解决。 这可以保证执行整个方法。 任务可以包含由 Task 保护的多个 await 语句。 当 StreamWebSocket 或 MessageWebSocket 用作传输时,此模式应与 ControlChannelTrigger 对象一起使用。 对于可能需要很长时间才能完成的操作(例如典型的异步读取操作),应用应使用之前讨论的原始异步模式。
以下示例在 StreamWebSocket 上为推送通知和 WebSocket 保持连接注册 ControlChannelTrigger。
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 配合使用时,需要注意一些特殊注意事项。 在 ControlChannelTrigger 中使用 HttpClient 时,应遵循一些特定于传输的使用模式和最佳做法。 此外,这些注意事项会影响处理在 HttpClient 上接收数据包的请求的方式。
注意,当前网络触发器功能和 ControlChannelTrigger 均不支持使用 SSL 的 HttpClient。 将 HttpClient 与 ControlChannelTrigger 配合使用时,应遵循以下使用模式和最佳做法:
- 在将请求发送到特定 URI 之前,应用可能需要在 System.Net.Http 命名空间中的 HttpClient 或 HttpClientHandler 对象上设置各种属性和标头。
- 在创建要与 ControlChannelTrigger 一起使用的 HttpClient 传输之前,应用可能需要进行初始请求来测试和设置传输。 应用确定传输可以正确设置后,可以将 HttpClient 对象配置为与 ControlChannelTrigger 对象一起使用的传输对象。 此过程旨在防止某些情况破坏在传输层上建立的连接。 使用带证书的 SSL 时,应用可能需要显示一个对话框,以便输入 PIN 码,或者在有多个证书可供选择时进行选择。 可能需要代理身份验证和服务器身份验证。 如果代理或服务器身份验证过期,可能会关闭连接。 应用可以处理这些身份验证过期问题的一种方法是设置计时器。 需要 HTTP 重定向时,不能保证可以可靠地建立第二个连接。 初始测试请求将确保应用在将 HttpClient 对象用作 ControlChannelTrigger 对象的传输机制之前,能够使用最新的重定向 URL。
与其他网络传输不同,无法将 HttpClient 对象直接传递到 ControlChannelTrigger 对象的 UsingTransport 方法中。 相反,必须专门构造 HttpRequestMessage 对象,以便与 HttpClient 对象和 ControlChannelTrigger 一起使用。 HttpRequestMessage 对象是使用 RtcRequestFactory.Create 方法创建的。 然后,创建的 HttpRequestMessage 对象将传递给 UsingTransport 方法。
下面的示例演示如何构造用于 HttpClient 对象和 ControlChannelTrigger 的 HttpRequestMessage 对象。
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;
}
}
一些特殊注意事项会影响在 HttpClient 上发送 HTTP 请求以启动接收响应的请求的处理方式。 具体而言,在 ControlChannelTrigger 中使用 HttpClient 时,应用必须使用 Task 来处理发送而不是 await 模型。
使用 HttpClient,不会将 ControlChannelTrigger 的后台任务上的 IBackgroundTask.Run 方法与接收完成回调的返回同步。 因此,应用只能在 Run 方法中使用阻止 HttpResponseMessage 技术,并等待收到整个响应。
将 HttpClient 与 ControlChannelTrigger 配合使用,与 StreamSocket、MessageWebSocket 或 StreamWebSocket 等传输方式明显不同。 由于 HttpClient 代码,通过 Task 将 HttpClient 接收回调提供给应用。 这意味着,一旦将数据或错误调度到应用, ControlChannelTrigger 推送通知任务就会触发。 在下面的示例中,代码将 HttpClient.SendAsync 方法返回的 responseTask 存储到全局存储中,推送通知任务会获取该任务并以内联方式进行处理。
以下示例演示如何在与 ControlChannelTrigger 一起使用时处理 HttpClient 上的发送请求。
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);
}
以下示例演示如何在与 ControlChannelTrigger 一起使用时读取 HttpClient 上收到的响应。
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 配合使用的详细信息,请参阅 ControlChannelTrigger HttpClient 示例。
ControlChannelTrigger 与 IXMLHttpRequest2 配合使用
将 IXMLHTTPRequest2 与 ControlChannelTrigger 配合使用时,需要注意一些特殊注意事项。 将 IXMLHTTPRequest2 与 ControlChannelTrigger 配合使用时,应遵循一些特定于传输的使用模式和最佳做法。 使用 ControlChannelTrigger 不会影响处理 IXMLHTTPRequest2 上发送或接收 HTTP 请求的请求的方式。
将 IXMLHTTPRequest2 与 ControlChannelTrigger 配合使用时的使用模式和最佳做法
- 用作传输的 IXMLHTTPRequest2 对象只有一个请求/响应的生存期。 与 ControlChannelTrigger 对象一起使用时,创建并设置 ControlChannelTrigger 对象一次很方便,然后重复调用 UsingTransport 方法,每次关联新的 IXMLHTTPRequest2 对象。 应用应在提供新的 IXMLHTTPRequest2 对象之前删除以前的 IXMLHTTPRequest2 对象,以确保应用不超过分配的资源限制。
- 应用可能需要调用 SetProperty 和 SetRequestHeader 方法才能在调用 Send 方法之前设置 HTTP 传输。
- 在创建要与 ControlChannelTrigger 一起使用的传输之前,应用可能需要进行初始发送请求来测试和设置传输。 应用确定传输设置正确后, 可以将 IXMLHTTPRequest2 对象配置为与 ControlChannelTrigger 一起使用的传输对象。 此过程旨在防止某些情况破坏在传输层上建立的连接。 当应用使用带证书的 SSL 时,可能需要显示一个对话框,以便输入 PIN 码,或者在有多个证书可供选择时进行选择。 可能需要代理身份验证和服务器身份验证。 如果代理或服务器身份验证过期,可能会关闭连接。 应用可以处理这些身份验证过期问题的一种方法是设置计时器。 需要 HTTP 重定向时,不能保证可以可靠地建立第二个连接。 初始测试请求将确保应用在将 IXMLHTTPRequest2 对象用作 ControlChannelTrigger 对象的传输机制之前,可以使用最新的重定向 URL。
有关将 IXMLHTTPRequest2 与 ControlChannelTrigger 配合使用的详细信息,请参阅 IXMLHTTPRequest2 示例的 ControlChannelTrigger。