65.9K
CodeProject 正在变化。 阅读更多。
Home

匿名管道变得简单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (19投票s)

2016年3月24日

CPOL

5分钟阅读

viewsIcon

56708

downloadIcon

2268

使用序列化在 C# 中创建匿名管道的简单便捷的包装类。

 

引言

有时,您只是需要能够与您的应用程序进行通信。

应该就是这么简单,不是吗?假设您有几个应用程序作为您的解决方案的一部分运行,而您只想让主应用程序能够告诉其他应用程序关闭。或者,您可能想启动一个应用程序,让它在后台运行,然后传回一些有用的信息。又或者,您有一个 Windows 服务与桌面应用程序运行在同一台机器上,而您需要它们之间能够相互通信,并且您真的不希望(也不应该)诉诸于使用 TCP/IP。

匿名管道就是解决这些问题的方案。然而,实现它们可能有点棘手。这个示例项目中 AnonymousPipes 类的目标是让使用它们尽可能简单直观,正如我认为它们最初应该的那样。

背景

我构建了几个 TCP/IP 通信库,并且我发现自己经常使用它们来解决解决方案中不同应用程序之间通信的问题。有时,我甚至会 tempted to communicate with different applications on the same machine through these libraries when I shouldn't, because it would be easy... 但实际上,当消息只会由同一网卡和计算机的其他应用程序接收时,没有理由通过网卡并将消息发送到网络。这既是一个网络安全问题,也是不必要的网络带宽使用。

这里正确的方法是 IPC(进程间通信)——特别是匿名管道(而不是**命名**管道,我在这里不讨论它们),因为我们只需要同一台 PC 或服务器上正在运行的应用程序之间的通信。

使用匿名管道需要一些设置,而且匿名管道还有一个恼人的限制,即它是一个单向通信通道……所以,如果您想让主应用程序和子应用程序之间进行双向通信,您就需要设置两个管道。

这似乎是一个绝佳的机会来编写一个处理所有这些设置和细节的包装类……完成后,我才意识到我可能在 CodeProject 上找到了我需要的东西。令我惊讶的是,我发现这里(或者我的搜索没有找到)没有 C# 的匿名管道使用示例。

Using the Code

在示例项目中,我只是在 Windows Forms 应用程序启动时检查是否有任何命令行参数。如果没有,那么我就假设这个实例将是管道服务器,所以我立即启动应用程序的另一个实例,并将两个管道句柄(输入和输出管道)作为命令行参数传递给新实例。

第二个实例启动并发现它确实有一些命令行参数,因此它假定自己是一个管道客户端。它读取第一个参数,即我们第一个实例创建的两个管道的句柄,并连接到这两个管道。

下面是一个实例化 AnonymousPipes 包装类的示例。我认为这可能有点令人困惑,所以我将详细介绍这个实例化过程。

pipe = new AnonymousPipes("Server end of the pipe.", (object msg) => {
   // Text in:
   UI(() => lbTextIn.Items.Add("Object received: " + msg.GetType().ToString() + " (" + msg.ToString() + ")")));
}, ()=> {
   // A client has connected!
   UI(() => tsbStatus.Text = "Client Connected");
   UI(() => tsbStartClient.Enabled = false);
}, () => {
   // A client disconnected!
   UI(() => lbTextIn.Items.Add("Client disconnected at " + DateTime.Now.ToString()));

   // Shut down this AnonymousPipes server:
   pipe.Close();

   // And start it listening again
   // after the disconnection:
   if(!tsbStartServer.Visible) StartAnonymousPipesServer();
}, out handles);

AnonymousPipes 类根据实例化它的对象不同而有不同的实例化方式:管道服务器或管道客户端。

如果实例化的是管道服务器,那么 AnonymousPipes 包装类需要进行一些初始设置——包括启动子应用程序并将包含两个管道句柄的命令行参数传递给它。

因为它是一个包装类,并且我希望它尽可能可重用,所以我使用委托来从包装类接收传入的文本。

管道**服务器**应用程序使用的 AnonymousPipes 构造函数如下所示:

public AnonymousPipes(String pipeName, CallBack callback, 
  ConnectionEvent connectionEvent, ConnectionEvent disconnectEvent, out Handles handles)
{
}

我认为参数显而易见:pipeName 是您给管道起的名称,以便在您将 AnonymousPipes 对象添加到列表中时可以跟踪它。您可以通过调用 .GetPipeName() 来获取此对象的名称。

回调委托具有以下签名:

public delegate void CallBack(object msg);

这用于将管道中的传入文本传递给您。任何传入的文本都将以这种方式到达。您应该使用具有正确签名的函数(例如 private void YourFunction(String msg) { }),或者像我一样使用匿名内联委托。在此示例项目中,我只是将所有进入的内容放入一个列表框中,以便您可以看到它。

connectionEventdisconnectEvent 也是委托。如果您的子应用程序断开连接,或者来自包装类的连接事件触发,它们就会触发。使用此来处理您需要的任何清理工作,以防万一您的子应用程序断开连接或关闭。

在客户端应用程序中,包装类实例化如下:

pipe = new AnonymousPipes("Client end of the pipe.");

if (!pipe.ConnectToPipe(startArgs, (object msg) => {
   UI(() => lbTextIn.Items.Add("Object received:" + msg.GetType().ToString() + " (" + msg.ToString() + ")"));
}, () => {
   // We're disconnected!
   UI(() => tsbStatus.Text = "Disconnected");
   UI(() => tsbConnectToPipe.Enabled = false);

   pipe.Close();
})) {
   // Connection failed!
   UI(() => tsbConnectToPipe.Enabled = false);
   UI(() => tsbStatus.Text = "Connection failed: The current handles are invalid!");
   return;
}

在客户端实例化包装类时,只需传递名称。然后调用 ConnectToPipe(),如上所示。委托应该很熟悉,并且其工作方式与管道服务器实例化中的完全相同。

序列化

在此示例项目中,我再次使用二进制格式化程序进行序列化。使用它很简单直接——只需将任何 .net 可序列化对象放入 Send() 函数即可。要发送您自己的类/对象,请确保它们同时存在于您的服务器和客户端应用程序中,它们是相同的,并且它们都被标记为 [Serializable]。

历史

  • 2016 年 3 月 24 日:初始版本
  • 2022 年 2 月 27 日 - 添加了序列化。
© . All rights reserved.