重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel)
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含15954字,纯文字阅读大概需要23分钟。
内容图文
原文:重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel)
作者:webabcd
介绍
重新想象 Windows 8 Store Apps
之 后台任务
- 控制通道(ControlChannel)
示例
1、客户端与服务端做 ControlChannel
通信的关键代码
ControlChannelHelper/AppContext.cs
/* * 本例通过全局静态变量来实现 app 与 task 的信息共享,以便后台任务可以获取到 app 中的相关信息 * * 注: * 也可以通过 Windows.ApplicationModel.Core.CoreApplication.Properties 保存数据,以实现 app 与 task 的信息共享 */using System.Collections.Concurrent; using Windows.Networking.Sockets; namespace ControlChannelHelper { publicclass AppContext { ///<summary>/// 从 ControlChannel 接收到的数据 ///</summary>publicstatic ConcurrentQueue<string> MessageQueue = new ConcurrentQueue<string>(); ///<summary>/// 客户端 socket ///</summary>publicstatic StreamSocket ClientSocket; } }
ControlChannelHelper/SocketControlChannel.cs
/* * 实现一个 socket tcp 通信的 ControlChannel,client 将在此 ControlChannel 中实时接收数据 * * 注: * win8 client 和 socket server 不能部署在同一台机器上,否则会抛出异常:{参考的对象类型不支持尝试的操作。 (异常来自 HRESULT:0x8007273D)} */using System; using System.Threading.Tasks; using Windows.ApplicationModel.Background; using Windows.Foundation; using Windows.Networking; using Windows.Networking.Sockets; using Windows.Storage.Streams; namespace ControlChannelHelper { publicclass SocketControlChannel : IDisposable { // ControlChannelpublic ControlChannelTrigger Channel { get; set; } // 客户端 socketprivate StreamSocket _socket; // 用于发送数据private DataWriter _dataWriter; // 用于接收数据private DataReader _dataReader; // 向服务端发送心跳的间隔时间,单位为分钟,最小 15 分钟privateuint _serverKeepAliveInterval = 15; // ControlChannel 的标识privatestring _channelId = "myControlChannel"; public SocketControlChannel() { } publicasync Task<string> CreateChannel() { Dispose(); try { // 实例化一个 ControlChannel Channel = new ControlChannelTrigger(_channelId, _serverKeepAliveInterval, ControlChannelTriggerResourceType.RequestHardwareSlot); } catch (Exception ex) { Dispose(); return"控制通道创建失败:" + ex.ToString(); } // 注册用于向服务端 socket 发送心跳的后台任务,需要在 manifest 中做相关配置var keepAliveBuilder = new BackgroundTaskBuilder(); keepAliveBuilder.Name = "myControlChannelKeepAlive"; // 注:如果走的是 WebSocket 协议,则系统已经为其内置了发送心跳的逻辑,此处直接指定为 Windows.Networking.Sockets.WebSocketKeepAlive 即可 keepAliveBuilder.TaskEntryPoint = "BackgroundTaskLib.ControlChannelKeepAlive"; keepAliveBuilder.SetTrigger(Channel.KeepAliveTrigger); // 到了发送心跳的间隔时间时则触发,本例是 15 分钟 keepAliveBuilder.Register(); // 注册用于向用户显示通知的后台任务,需要在 manifest 中做相关配置var pushNotifyBuilder = new BackgroundTaskBuilder(); pushNotifyBuilder.Name = "myControlChannelPushNotification"; pushNotifyBuilder.TaskEntryPoint = "BackgroundTaskLib.ControlChannelPushNotification"; pushNotifyBuilder.SetTrigger(Channel.PushNotificationTrigger); // 在 ControlChannel 中收到了推送过来的数据时则触发 pushNotifyBuilder.Register(); try { _socket = new StreamSocket(); AppContext.ClientSocket = _socket; // 在 ControlChannel 中通过指定的 StreamSocket 通信 Channel.UsingTransport(_socket); // client socket 连接 server socketawait _socket.ConnectAsync(new HostName("192.168.6.204"), "3366"); // 开始等待 ControlChannel 中推送过来的数据,如果 win8 client 和 socket server 部署在同一台机器上,则此处会抛出异常 ControlChannelTriggerStatus status = Channel.WaitForPushEnabled(); if (status != ControlChannelTriggerStatus.HardwareSlotAllocated && status != ControlChannelTriggerStatus.SoftwareSlotAllocated) return"控制通道创建失败:" + status.ToString(); // 发送数据到服务端 _dataWriter = new DataWriter(_socket.OutputStream); string message = "hello " + DateTime.Now.ToString("hh:mm:ss") + "^"; _dataWriter.WriteString(message); await _dataWriter.StoreAsync(); // 接收数据 ReceiveData(); } catch (Exception ex) { Dispose(); return"控制通道创建失败:" + ex.ToString(); } return"ok"; } // 开始接收此次数据privatevoid ReceiveData() { uint maxBufferLength = 256; try { var buffer = new Windows.Storage.Streams.Buffer(maxBufferLength); var asyncOperation = _socket.InputStream.ReadAsync(buffer, maxBufferLength, InputStreamOptions.Partial); asyncOperation.Completed = (IAsyncOperationWithProgress<IBuffer, uint> asyncInfo, AsyncStatus asyncStatus) => { switch (asyncStatus) { case AsyncStatus.Completed: case AsyncStatus.Error: try { IBuffer bufferRead = asyncInfo.GetResults(); uint bytesRead = bufferRead.Length; _dataReader = DataReader.FromBuffer(bufferRead); // 此次数据接收完毕 ReceiveCompleted(bytesRead); } catch (Exception ex) { AppContext.MessageQueue.Enqueue(ex.ToString()); } break; case AsyncStatus.Canceled: AppContext.MessageQueue.Enqueue("接收数据时被取消了"); break; } }; } catch (Exception ex) { AppContext.MessageQueue.Enqueue(ex.ToString()); } } publicvoid ReceiveCompleted(uint bytesRead) { // 获取此次接收到的数据uint bufferLength = _dataReader.UnconsumedBufferLength; string message = _dataReader.ReadString(bufferLength); // 将接收到的数据放到内存中,由 PushNotificationTrigger 触发的后台任进行处理(当然也可以在此处处理) AppContext.MessageQueue.Enqueue(message); // 开始接收下一次数据 ReceiveData(); } // 释放资源publicvoid Dispose() { lock (this) { if (_dataWriter != null) { try { _dataWriter.DetachStream(); _dataWriter = null; } catch (Exception ex) { } } if (_dataReader != null) { try { _dataReader.DetachStream(); _dataReader = null; } catch (Exception exp) { } } if (_socket != null) { _socket.Dispose(); _socket = null; } if (Channel != null) { Channel.Dispose(); Channel = null; } } } } }
2、客户端辅助类
BackgroundTaskLib/ControlChannelKeepAlive.cs
/* * 用于向服务端 socket 发送心跳的后台任务 * * 注: * 如果走的是 WebSocket 协议,则系统已经为其内置了发送心跳的逻辑 * 只需要将 BackgroundTaskBuilder.TaskEntryPoint 设置为 Windows.Networking.Sockets.WebSocketKeepAlive 即可,而不需要再自定义此后台任务 */using ControlChannelHelper; using System; using Windows.ApplicationModel.Background; using Windows.Networking.Sockets; using Windows.Storage.Streams; namespace BackgroundTaskLib { publicsealedclass ControlChannelKeepAlive : IBackgroundTask { publicvoid Run(IBackgroundTaskInstance taskInstance) { if (taskInstance == null) return; // 获取 ControlChannelvar channelEventArgs = taskInstance.TriggerDetails as IControlChannelTriggerEventDetails; ControlChannelTrigger channel = channelEventArgs.ControlChannelTrigger; if (channel == null) return; string channelId = channel.ControlChannelTriggerId; // 发送心跳 SendData(); } privateasyncvoid SendData() { // 发送心跳到 server socket DataWriter dataWriter = new DataWriter(AppContext.ClientSocket.OutputStream); string message = "hello " + DateTime.Now.ToString("hh:mm:ss") + "^"; dataWriter.WriteString(message); await dataWriter.StoreAsync(); } } }
BackgroundTaskLib/ControlChannelPushNotification.cs
/* * 用于向用户显示通知的后台任务,需要在 manifest 中做相关配置 */using ControlChannelHelper; using NotificationsExtensions.ToastContent; using System; using Windows.ApplicationModel.Background; using Windows.Networking.Sockets; using Windows.UI.Notifications; namespace BackgroundTaskLib { publicsealedclass ControlChannelPushNotification : IBackgroundTask { publicvoid Run(IBackgroundTaskInstance taskInstance) { if (taskInstance == null) return; // 获取 ControlChannelvar channelEventArgs = taskInstance.TriggerDetails as IControlChannelTriggerEventDetails; ControlChannelTrigger channel = channelEventArgs.ControlChannelTrigger; if (channel == null) return; string channelId = channel.ControlChannelTriggerId; try { string messageReceived; // 将从 ControlChannel 中接收到的信息,以 toast 的形式弹出while (AppContext.MessageQueue.Count > 0) { bool result = AppContext.MessageQueue.TryDequeue(out messageReceived); if (result) { IToastText01 templateContent = ToastContentFactory.CreateToastText01(); templateContent.TextBodyWrap.Text = messageReceived; templateContent.Duration = ToastDuration.Short; IToastNotificationContent toastContent = templateContent; ToastNotification toast = toastContent.CreateNotification(); ToastNotifier toastNotifier = ToastNotificationManager.CreateToastNotifier(); toastNotifier.Show(toast); } } } catch (Exception ex) { } } } }
3、客户端
BackgroundTask/ControlChannel.xaml
<Page x:Class="XamlDemo.BackgroundTask.ControlChannel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.BackgroundTask" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"><Grid Background="Transparent"><StackPanel Margin="120 0 0 0"><TextBlock Name="lblMsg" FontSize="14.667"/><Button Name="btnCreateChannel" Content="创建一个 ControlChannel" Margin="0 10 0 0" Click="btnCreateChannel_Click"/></StackPanel></Grid></Page>
BackgroundTask/ControlChannel.xaml.cs
/* * 演示如何创建一个基于 socket tcp 通信的 ControlChannel,client 将在此 ControlChannel 中实时接收数据 * * 注: * 不能在模拟器中运行 * RTC - Real Time Communication 实时通信 * win8 client 和 socket server 不能部署在同一台机器上,否则会抛出异常:{参考的对象类型不支持尝试的操作。 (异常来自 HRESULT:0x8007273D)} */using System; using ControlChannelHelper; using Windows.ApplicationModel.Background; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Popups; namespace XamlDemo.BackgroundTask { publicsealedpartialclass ControlChannel : Page { public ControlChannel() { this.InitializeComponent(); } privateasyncvoid btnCreateChannel_Click(object sender, RoutedEventArgs e) { // 如果 app 在锁屏上,则可以通过 ControlChannelTrigger 触发指定的后台任务 BackgroundAccessStatus status = BackgroundExecutionManager.GetAccessStatus(); if (status == BackgroundAccessStatus.Unspecified) { status = await BackgroundExecutionManager.RequestAccessAsync(); } if (status == BackgroundAccessStatus.Denied) { awaitnew MessageDialog("请先将此 app 添加到锁屏").ShowAsync(); return; } // 创建一个基于 socket tcp 通信的 ControlChannel,相关代码参见:ControlChannelHelper 项目 SocketControlChannel channel = new SocketControlChannel(); string result = await channel.CreateChannel(); lblMsg.Text = result; } } }
4、服务端
SocketServerTcp/ClientSocketPacket.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SocketServerTcp { ///<summary>/// 对客户端 Socket 及其他相关信息做一个封装 ///</summary>publicclass ClientSocketPacket { ///<summary>/// 客户端 Socket ///</summary>public System.Net.Sockets.Socket Socket { get; set; } privatebyte[] _buffer; ///<summary>/// 为该客户端 Socket 开辟的缓冲区 ///</summary>publicbyte[] Buffer { get { if (_buffer == null) _buffer = newbyte[64]; return _buffer; } } private List<byte> _receivedByte; ///<summary>/// 客户端 Socket 发过来的信息的字节集合 ///</summary>public List<byte> ReceivedByte { get { if (_receivedByte == null) _receivedByte = new List<byte>(); return _receivedByte; } } } }
SocketServerTcp/Main.cs
/* * 从以前写的 wp7 demo 中直接复制过来的,用于演示如何通过 ControlChannel 实时地将信息以 socket tcp 的方式推送到 win8 客户端 * * 注: * 本例通过一个约定结束符来判断是否接收完整,其仅用于演示,实际项目中请用自定义协议。可参见:XamlDemo/Communication/TcpDemo.xaml.cs */using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.Threading; using System.IO; namespace SocketServerTcp { publicpartialclass Main : Form { SynchronizationContext _syncContext; System.Timers.Timer _timer; // 信息结束符,用于判断是否完整地读取了客户端发过来的信息,要与客户端的信息结束符相对应(本例只用于演示,实际项目中请用自定义协议)privatestring _endMarker = "^"; // 服务端监听的 socketprivate Socket _listener; // 实例化 ManualResetEvent,设置其初始状态为无信号private ManualResetEvent _signal = new ManualResetEvent(false); // 客户端 Socket 列表private List<ClientSocketPacket> _clientList = new List<ClientSocketPacket>(); public Main() { InitializeComponent(); // UI 线程 _syncContext = SynchronizationContext.Current; // 启动后台线程去运行 Socket 服务 Thread thread = new Thread(new ThreadStart(LaunchSocketServer)); thread.IsBackground = true; thread.Start(); } privatevoid LaunchSocketServer() { // 每 10 秒运行一次计时器所指定的方法,群发信息 _timer = new System.Timers.Timer(); _timer.Interval = 10000d; _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed); _timer.Start(); // TCP 方式监听 3366 端口 _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _listener.Bind(new IPEndPoint(IPAddress.Any, 3366)); // 指定等待连接队列中允许的最大数 _listener.Listen(10); while (true) { // 设置为无信号 _signal.Reset(); // 开始接受客户端传入的连接 _listener.BeginAccept(new AsyncCallback(OnClientConnect), null); // 阻塞当前线程,直至有信号为止 _signal.WaitOne(); } } privatevoid _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 每 10 秒给所有连入的客户端发送一次消息 SendData(string.Format("webabcd 对所有人说:大家好! 【信息来自服务端 {0}】", DateTime.Now.ToString("hh:mm:ss"))); } privatevoid OnClientConnect(IAsyncResult async) { ClientSocketPacket client = new ClientSocketPacket(); // 完成接受客户端传入的连接的这个异步操作,并返回客户端连入的 socket client.Socket = _listener.EndAccept(async); // 将客户端连入的 Socket 放进客户端 Socket 列表 _clientList.Add(client); OutputMessage(((IPEndPoint)client.Socket.LocalEndPoint).Address + " 连入了服务器"); SendData("一个新的客户端已经成功连入服务器。。。 【信息来自服务端】"); try { // 开始接收客户端传入的数据 client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(OnDataReceived), client); } catch (SocketException ex) { // 处理异常 HandleException(client, ex); } // 设置为有信号 _signal.Set(); } privatevoid OnDataReceived(IAsyncResult async) { ClientSocketPacket client = async.AsyncState as ClientSocketPacket; int count = 0; try { // 完成接收数据的这个异步操作,并返回接收的字节数if (client.Socket.Connected) count = client.Socket.EndReceive(async); } catch (SocketException ex) { HandleException(client, ex); } // 把接收到的数据添加进收到的字节集合内 // 本例采用 UTF8 编码,中文占用 3 字节,英文等字符与 ASCII 相同foreach (byte b in client.Buffer.Take(count)) { if (b == 0) continue; // 如果是空字节则不做处理(‘\0‘) client.ReceivedByte.Add(b); } // 把当前接收到的数据转换为字符串。用于判断是否包含自定义的结束符string receivedString = UTF8Encoding.UTF8.GetString(client.Buffer, 0, count); // 如果该 Socket 在网络缓冲区中没有排队的数据 并且 接收到的数据中有自定义的结束符时if (client.Socket.Connected && client.Socket.Available == 0 && receivedString.Contains(_endMarker)) { // 把收到的字节集合转换成字符串(去掉自定义结束符) // 然后清除掉字节集合中的内容,以准备接收用户发送的下一条信息string content = UTF8Encoding.UTF8.GetString(client.ReceivedByte.ToArray()); content = content.Replace(_endMarker, ""); client.ReceivedByte.Clear(); // 发送数据到所有连入的客户端,并在服务端做记录 SendData(content); OutputMessage(content); } try { // 继续开始接收客户端传入的数据if (client.Socket.Connected) client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(OnDataReceived), client); } catch (SocketException ex) { HandleException(client, ex); } } ///<summary>/// 发送数据到所有连入的客户端 ///</summary>///<param name="data">需要发送的数据</param>privatevoid SendData(string data) { byte[] byteData = UTF8Encoding.UTF8.GetBytes(data); foreach (ClientSocketPacket client in _clientList) { if (client.Socket.Connected) { try { // 如果某客户端 Socket 是连接状态,则向其发送数据 client.Socket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnDataSent), client); } catch (SocketException ex) { HandleException(client, ex); } } else { // 某 Socket 断开了连接的话则将其关闭,并将其清除出客户端 Socket 列表 // 也就是说每次向所有客户端发送消息的时候,都会从客户端 Socket 集合中清除掉已经关闭了连接的 Socket client.Socket.Close(); _clientList.Remove(client); } } } privatevoid OnDataSent(IAsyncResult async) { ClientSocketPacket client = async.AsyncState as ClientSocketPacket; try { // 完成将信息发送到客户端的这个异步操作int sentBytesCount = client.Socket.EndSend(async); } catch (SocketException ex) { HandleException(client, ex); } } ///<summary>/// 处理 SocketException 异常 ///</summary>///<param name="client">导致异常的 ClientSocketPacket</param>///<param name="ex">SocketException</param>privatevoid HandleException(ClientSocketPacket client, SocketException ex) { // 在服务端记录异常信息,关闭导致异常的 Socket,并将其清除出客户端 Socket 列表 OutputMessage(client.Socket.RemoteEndPoint.ToString() + " - " + ex.Message); client.Socket.Close(); _clientList.Remove(client); } // 在 UI 上输出指定信息privatevoid OutputMessage(string data) { _syncContext.Post((p) => { txtMsg.Text += p.ToString() + "\r\n"; }, data); } } }
OK
[源码下载]
原文:http://www.cnblogs.com/lonelyxmas/p/3590269.html
内容总结
以上是互联网集市为您收集整理的重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel)全部内容,希望文章能够帮你解决重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel)所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。