使用 P2PMessageQueue
本部分,您将看到使用 P2PMessageQueue 类和相关类型的示例。
注 当运行该示例时,可以选择部署到 Windows CE 或 Pocket PC 2003 模拟器或设备。您可以在不进行修改的情况下在任一个平台上调试该项目(并运行该应用程序)。运行示例时,看到的第一个屏幕提示您在阅读器进程和发送器进程间进行选择。无论选择哪一个,都必须再次运行 .exe 文件(从 Program Files),然后选择另一个选项。在 Pocket PC 平台上,无论是使用模拟器还是设备,都必须重命名 .exe 文件(否则将激活现有的运行中应用程序)。
字符串的简单 IPC 交换
首先使用托管进程将字符串传入另一个 .NET Compact Framework 应用程序(也可以使发送方或接收方成为本机应用程序。有三种不同的方法用来读取接收端的字符串(相同的原理也适用于发送端):阻塞、非阻塞以及事件驱动。
发送方和接收方的图形用户界面 (GUI) 在功能方面是自描述性的,如图 3、4 和 5 所示。
图 3. 主窗体
图 4. 读取端
当发送方单击 Send 按钮时,发送一个字符串(如文本框中输入的),并可以选择将该消息设置为警告消息(基于 Message Is Alert 复选框状态)。发送方会阻塞,直到针对指定超时发送该消息(作为 combobox 中选择的发送方)。位于该复选框下面的窗体底部显示 Send 方法的返回结果,如图 5 所示(即 OK)。
图 5. 发送 / 编写端
这里再次使用了下载示例中的 Send 方法。
- private void cmdSend_Click(object sender, System.EventArgs e) {
- Message msg;
- msg = new Message(
- System.Text.Encoding.ASCII.GetBytes(txtSend.Text),
- chkIsAlert.Checked);
- ReadWriteResult rwr = mQue.Send(msg, mTimeout);
- lblSendResult.Text = rwr.ToString();
- }
当阅读器收到一个消息后,会将它显示在列表视图(第三列)中,并指出它是否是警告消息(第二列)。当成功接收到该消息时,第一列将始终显示 OK。默认情况下,要接收一个消息,请单击 Receive 按钮;如果没有消息要接收或者方法失败,则列表视图的第一列将指出原因(另两列在该情形中不适用)。
在读取和发送时,Queue Info 菜单(单击 Info,然后单击 Queue Info)会显示有关队列的数据。接收端上的 Mode 菜单(单击 Read,然后单击 Mode)有三个菜单项:On Demand Only、Event driven 和 Block a Thread。这些项用于配置该程序如何接收队列外的消息。当您选择一个模式后,它在示例应用程序的生命周期内不应该更改(开发人员可针对自己的设计进行混合与匹配)。以下几个小节描述三种读取模式。
按命令读(对应于菜单 On Demand Only )
当接收方单击 Receive 按钮时,将执行以下方法。
- private void cmdReceive_Click(object sender, System.EventArgs e) {
- Message msg;
- msg = new Message();
- // mTimeout is set by the end user by means of the GUI
- // to DON'T BLOCK (0), BLOCK (-1), or a real timeout value
- ReadWriteResult rwr = mQue.Receive(ref msg, mTimeout);
- ListViewItem lvi;
- if (rwr == ReadWriteResult.OK){
- bool isAlrt;
- string payload;
- isAlrt = msg.IsAlert;
- byte[] bytes = msg.MessageBytes;
- payload = System.Text.Encoding.ASCII.GetString(
- bytes, 0, bytes.GetLength(0));
- lvi = new ListViewItem(
- new string[]{rwr.ToString(), isAlrt.ToString(), payload});
- }else{
- lvi = new ListViewItem(
- new string[]{rwr.ToString(), @"n\a", @"n\a"});
- }
- listView1.Items.Add(lvi);
- listView1.Columns[2].Width = -2;}
事件驱动
事件驱动模型基本上意味着应用程序不会在任意时刻通过调用 Receive(例如,在计时器上或者要求用户单击 Receive 按钮)来轮询新消息,相反,应用程序会订阅并捕获来自 P2PMessageQueue 类的事件。要订阅事件,需要使用正规的 .NET Compact Framework 委托习语(队列的创建也不例外)。
- mQue = new P2PMessageQueue(
- isReader, txtQueueName.Text, maxL, maxM, out firstTime);
- mQue.DataOnQueueChanged += new EventHandler(mQue_DataOnQueueChanged);
引发该事件会调用方法,在本例中,只调用现有的接收方法。
- private void mQue_DataOnQueueChanged(object sender, EventArgs e) {
- this.Invoke(new EventHandler(this.cmdReceive_Click));
- }
阻塞线程
第三种从队列进行读取的方法是:创建一个线程,并使其阻塞以等待队列的 Receive 方法。每次接收到消息时,应用程序都会处理它,然后再次循环回阻塞。以下是一些带有解释的示例代码。
您会在某个地方创建并启动以下线程。
- Thread t = new Thread(new ThreadStart(ThreadBlockedOnRead));
- t.Start();
该线程用下面的方法运行。(有关更多上下文,请下载代码)。
- private void ThreadBlockedOnRead(){
- while (mMode == 2){ // Thread mode
- Message msg = new Message();
- //Can actually omit a timeout for a true infinite block
- ReadWriteResult rwr = mQue.Receive(msg, 60 * 1000);
- if (rwr == ReadWriteResult.InvalidHandle || mMode != 2){
- return;
- }
- string body = rwr.ToString();
- if (rwr == ReadWriteResult.OK){
- byte[] bytes = msg.MessageBytes;
- string bytesAsString =
- System.Text.Encoding.ASCII.GetString(
- bytes, 0, bytes.GetLength(0));
- body += " | " + msg.IsAlert.ToString() + " | " + bytesAsString;
- }
- MessageBox.Show(body, "To terminate this mode use the menu again");
- }
- }
基本示例代码至此结束。读取队列的三种方式也可以应用于通过队列发送。当事件接收到信号时(即,队列从已满转变为未满),可进行阻塞和发送或尝试在不进行阻塞的情况下随时进行发送。在下一部分中,您将看到该设计如何允许队列中的消息包含其他结构 — 而不仅仅是字符串。
注 在开发一个依赖事件信号来识别队列从已满转变为未满的应用程序时,需要在应用程序启动时针对队列执行一个初始写入操作。如果不执行该初始写入操作,则应用程序永远不会开始写入,因为该初始写入操作必须经过特定地执行才能填充转变为未满状态的队列,因此,向事件发出信号以触发针对该队列的进一步写入操作。
发送和接收更复杂的类型(不仅仅是字符串)
在前几部分中,是在应用程序之间传递字符串,但如果可以将字符串与字节数组进行转换,则还可以传递任何数据类型。因此,将该类型转换为一个字节数组,在其中创建 Message 类,然后发送 Message。在接收端,检索 Message,获取字节数组,然后在其中创建该类型(例如,公开类型的 ToBytes 和 FromBytes 方法)。另一个方法是,从 Message 类继承自己的类,并在其中实现转换。很自然,如果您尝试传递一个复杂的对象图,则在类型与字节数组之间进行转换会难得多。尝试使用没有源代码的类型需要特别注意,因为您可能不具有对该类型的完整状态(例如,私有成员)的访问权,因此,可能无法准确地在类型与字节数组之间进行转换。
出于简单的目的,假设将一个 Int64 和一个 Boolean 从一个进程传递到另一个进程。将创建一个 CustomMessage 类,如下所示。
- public class CustomMessage : Message {
- public long TotalMemory;
- public bool AfterGC;
- public CustomMessage(){
- TotalMemory = 0;
- AfterGC = false;
- }
- public CustomMessage(long totMem, bool afterGarbCol){
- TotalMemory = totMem;
- AfterGC = afterGarbCol;
- }
- public override byte[] MessageBytes {
- get {
- byte[] b1 = BitConverter.GetBytes(TotalMemory);
- byte[] b2 = BitConverter.GetBytes(AfterGC);
- byte[] b = new byte[9];
- Buffer.BlockCopy(b1, 0, b, 0, 8);
- Buffer.BlockCopy(b2, 0, b, 8, 1);
- return b;
- }
- set {
- TotalMemory = BitConverter.ToInt64(value, 0);
- AfterGC = BitConverter.ToBoolean(value, 8);
- base.MessageBytes = value;
- }
- }
- }
添加了两个感兴趣的字段(TotalMemory、AfterGC),重写 MessageBytes 属性以实现转换(get 和 set 方法位于转换发生的位置),然后添加一个默认的构造函数(这个参数化的构造函数是可选的)。
现在,如果要使用前面的示例,只需更改两处地方:
发送消息时,要创建一个 CustomMessage,而不是将值赋给 TotalMemory 和 AfterGC。
- //msg = new Message(. . .); //Instead of this line
- msg = new CustomMessage(GC.GetTotalMemory(false), false);
- ReadWriteResult rwr = mQue.Send(msg, mTimeout);
接收消息时,要创建一个 CustomMessage。
- //msg = new Message();
- msg = new CustomMessage(); //msg still declared as Message
- ReadWriteResult rwr = mQue.Receive(msg, mTimeout);
然后,在 mQue.Receive 返回时读取它的属性。
- //byte[] bytes = msg.MessageBytes;
- //payload = System.Text.Encoding.ASCII.GetString(. . .);
- payload = "Total Memory = " + ((CustomMessage)msg).TotalMemory.ToString() +
- (((CustomMessage)msg).AfterGC ? " after a GC" : " without forcing a GC");
【编辑推荐】