目录
介绍
有时,您只需要能够与您的应用程序对话。
它应该那么容易,不是吗?假设您有一些应用程序作为解决方案的一部分在运行,而您只希望您的主应用程序能够告诉其他应用程序关闭。或者,也许您想启动一个,让它在后台运行,然后传回一些有用的信息。或者,也许您有一个Windows服务与桌面应用程序在同一台机器上运行,并且您需要它们能够相互通信,而您真的不想(也不应该)求助于使用TCP/IP。
匿名管道是解决这些问题的方法。不过,实现它们可能有点棘手。这个示例项目中的AnonymousPipes类的目的是让使用它们变得像我认为它们应该放在首位一样简单和直观。
背景
我已经构建了几个TCP/IP通信库,并且我发现自己经常使用它们来解决我的解决方案中不同应用程序之间的通信问题。我有时会发现自己很想通过这些库与同一台机器上的不同应用程序进行通信,而我不应该这样做,因为这很容易……但是确实没有理由通过网卡将您的消息放到网络上, 当它只会被同一个网卡和计算机拾取但用于另一个应用程序时。这是一个网络安全问题以及不必要的网络带宽使用。
正确的方法是IPC(进程间通信)——特别是匿名管道(与命名管道相反,我不会在这里讨论),因为我们只希望在同一台PC或服务器上运行的应用程序之间进行通信。
使用匿名管道涉及一些设置,并且匿名管道是一种单向通信通道也有一个琐碎的限制......所以如果你想在你的主应用程序和子应用程序之间进行两种通信,你需要设置两个管道。
这似乎是编写一个为我处理所有这些设置和细节的包装类的绝佳机会......当我完成后,我突然想到我可能已经在CodeProject上找到了我想要的东西。我惊讶地发现这里没有(或者我的搜索没有找到)C#的匿名管道使用示例。
使用代码
在示例项目中,我只是检查Windows窗体应用程序启动时是否有任何命令行参数。如果没有,那么我假设这个实例将是管道服务器,所以我立即启动应用程序的另一个实例并将两个管道句柄(输入和输出管道)作为命令行参数传递给新实例。
第二个实例启动并发现它确实有一些命令行参数,所以它假设它是一个管道客户端。它读取第一个参数,即我们的第一个实例创建的管道的两个句柄,并连接到这两个管道。
下面是一个实例化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) { }),或者像我一样使用匿名内联委托。在这个示例项目中,我只是将所有进入列表框的内容都推到了一个列表框中,这样您就可以看到它了。
connectionEvent和disconnectEvent都是代理。如果您的子应用程序从包装类断开和连接事件,它们将触发。如果您的子应用程序断开连接或关机,请使用它来进行您需要的任何清理工作。
在客户端应用程序中,包装类的实例化如下:
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]。
https://www.codeproject.com/Articles/1087779/Anonymous-Pipes-Made-Easy