进程间通信

时间:2010-11-03 15:36:04

标签: delphi winapi ipc delphi-2007

我有两个应用程序:X和Y.
X是主应用程序,它处理大量XML文件。它有超过10年的历史,已有六种技术用于存储,处理和处理这些XML文件。
Y是我正在开发的一个调试工具,它可以处理和显示XML文件。更易读的形式。基本上,它只有一组样式表,可以检测XML格式,如果它识别格式,它会将XML转换为HTML,显示在TWebBrowser组件中。

问题:
当Y处于活动状态时,我希望X将其执行的任何XML发送到Y以进行显示。但只有当Y在运行时!如果Y没有运行,X就不会做任何事情。
Y的检测需要在任何时刻完成,需要快速。我考虑过使用TCP / IP通信,但是由于缺少Y而造成的延迟太长了。特别是因为有时会处理很多XML。与命名管道和类似的基于网络的解决方案相同的问题。我需要快速确定Y是否正在运行且可用,如果是,请快速发送XML然后继续X.
我还考虑使Y成为基于COM的应用程序或者可能添加基于COM的DLL允许进程间通信的事件。 DLL解决方案会很有趣,因为它会向X公开一个方法来上传XML文件,然后向Y发送一个事件来处理XML。 这似乎是最好的选择虽然我还需要检查DLL是否已注册。如果没有,则X甚至无法调用它!
应用程序X也将被不会收到Y或其他DLL的客户使用,因此在大多数情况下,DLL将不会被注册。 (正如我所说,这意味着在调试期间提供帮助......)

但也许还有其他选择吗? TCP / IP太慢,COM有点过于复杂。

X和Y将在同一系统上运行。或者只是X将在系统上,Y完全丢失。


关于使用内存映射文件......虽然实用,但我需要记住,大多数情况下,Y不会运行,因此MMF会浪费内存。 X中的XML数据最大可达4 MB,因此在内存中具有此大小的多个块有点矫枉过正。它可用于在X和Y之间发送状态消息,但内存有时是应用程序X的一个问题。虽然MMF可以连接到物理文件,但我试图完全避免写入任何临时文件。这是一个很好的解决方案,但我担心还不够好。


我认为还有一些额外的解释。应用程序X是一个应用程序,将使用几个小时,用户执行大量操作,转换为大量处理的XML数据。 Application X是一个桌面应用程序,它与多个Web应用程序(REST),Web服务(SOAP)和其他应用程序进行通信,其中大部分是通过XML进行的。
应用程序Y只是为了窥视X正在运行的进程。基本上,X工作20分钟,Y弹出。从那一刻开始,X应该开始向Y发送XML,直到Y再次消失或者X终止。在大多数情况下,Y只会运行以捕获正在运行的任务的一小部分,甚至可能多次启动。但我可能会想到整个方向都是错误的。也许X应该是Y注册的服务器......当Y找不到X时,这不是一个真正的问题。但是X找不到Y不会导致延迟或其他问题...

9 个答案:

答案 0 :(得分:11)

请查看我的IPC:

http://www.cromis.net/blog/downloads/cromis-ipc/

它快速,免费且具有可设置的超时,因此您可以将其设置为非常小的量(例如50ms)。因为它非常快(典型的消息周期请求 - >进程 - >响应时间不到1毫秒,大约0.1毫秒),您可以获得非常小的超时。它有客户端服务器构建,因此很多客户端没有问题。它运行线程与后面的任务池,因此它不会冻结您的程序,它具有非常灵活的数据包,以方便写入/读取数据。

如前所述,如果调试器正在运行,您甚至可以使用其他方式进行检查。

  • 检查流程
  • 检查流程的主窗口
  • 使用互斥锁
  • ...

答案 1 :(得分:6)

你可以让X将其输出写入memory-mapped file - 如果数据正在运行,Y可以检索数据。这样X不关心Y是否正常。

X可以在已知位置写入某种控制信息(例如,存储从映射文件中的偏移0开始的最后1000个写入的XML的偏移量),并使用文件的其余部分作为原始数据的循环缓冲区

如果您需要Y作为X中操作的决定因素,请让Y创建映射文件,然后使用其存在/不存在来检查“通道”X侧的数据生成。有创建者和第二个用户here的示例代码。

答案 2 :(得分:5)

你可以更简单地做到这一点,因为你只是试图找出一个应用程序是从另一个应用程序运行。只要它们由同一个用户在同一台​​机器上运行,您就可以让X只使用FindWindow()来查看Y当前是否正在运行。只要确保给Y一个有意义的名字(在下面的例子中,它是TXMLFormatterForm):

var
  XMLWindow: HWnd;
begin
  XMLWindow := FindWindow('TXMLFormatterForm', nil);
  if XMLWindow > 0 then
    // Y is running
end;

你也可以使用Y的窗口标题(标题),只要你确定它是独特的:

XMLWindow := FindWindow(nil, 'Workshop Alex's XML Formatter');

答案 3 :(得分:4)

命名管道很快,因为它们基于内存映射文件,用于实现。可能很慢的是服务器故障时的超时......

如果您需要在同一台计算机上快速响应,为什么不使用旧的GDI消息?

您可以在普通控制台或后台服务应用程序中使用这些消息,即使没有用户界面或可视化表单(如果是服务应用程序,安全设置必须指定此服务必须与桌面交互,即必须能够接收和发送消息)。

诀窍是处理WM_COPYDATA消息。 例如,参见我们的TSQLRestClientURIMessage类,以及TSQLRestServer的ExportServerMessage / AnswerToMessage方法,在http://synopse.info/fossil/finfo?name=SQLite3/SQLite3Commons.pas中实现

实际上,我发现GDI消息比命名管道要快得多,因为少量数据(每条消息高达64 KB或更高)。

您可以在Looking for an alternative to windows messages used in inter-process communication

中找到替代方案

答案 4 :(得分:2)

根据不同的客户/服务器调查,以下是一些关于速度的真实数据。

所有基准测试都在一台计算机上本地运行。您可以在直接访问时每秒执行超过15000次查询,在HTTP / 1.1远程访问上每秒可执行4300次查询。这是使用Centrino2 CPU和AntiVirus ON的笔记本电脑的基准测试。

2.5. Client server access: 
  - Http client keep alive: 3001 assertions passed
     first in 7.87ms, done in 153.37ms i.e. 6520/s, average 153us
  - Http client multi connect: 3001 assertions passed
     first in 151us, done in 305.98ms i.e. 3268/s, average 305us
  - Named pipe access: 3003 assertions passed
     first in 78.67ms, done in 187.15ms i.e. 5343/s, average 187us
  - Local window messages: 3002 assertions passed
     first in 148us, done in 112.90ms i.e. 8857/s, average 112us
  - Direct in process access: 3001 assertions passed
     first in 44us, done in 41.69ms i.e. 23981/s, average 41us
  Total failed: 0 / 15014  - Client server access PASSED

此基准测试测试客户端和服务器速度,并且不是多线程的(即使我们的框架是多线程安全的)。

因此,您可以猜测,对于每个请求,JSON内容的4 KB数据块:

  1. 直接访问(就像你的dll方法一样)是最快的,而且资源消耗更少。
  2. 然后是GDI消息。
  3. 然后命名管道。
  4. 然后是FastCGI和HTTP(取决于您的FastCGI Web服务器,HTTP类消耗非常低)。
  5. 保持活动连接是HTTP / 1.1连接,多连接是普通HTTP / 1.0,每个请求都有一个新连接。多连接并不是那么糟糕,因为从服务器的角度来看,我们使用了基于I / O完成端口的高效线程池。

    请参阅http://synopse.info/forum/viewtopic.php?id=90

答案 5 :(得分:1)

使用内存映射文件。它们非常适合您的特定任务。如果您没有时间或打算实施自定义方案,我们MsgConnect的MMF传输将会解决。

答案 6 :(得分:1)

我会去内存映射文件的方向,但我不会直接在代码路径中实现..我会通过一个中间对象来执行写入内存映射文件,这样它可以替换为如果数据不存在,那就放弃了数据。

当程序首次启动时(或被告知通过配置更改进行检查),系统将创建存根“不执行任何操作”或“通过内存映射文件记录”对象。这也使您能够在需要时添加更高版本的调试器...例如用于网络日志记录的UDP记录器等。

您可以使用“FindWindow”调用来查看调试器是否正在运行。

答案 7 :(得分:0)

如果您愿意使用临时文件作为传输,那么它将很容易

  1. 两个程序在启动时都会注册相同的自定义消息 MsgAppNotifyID:= RegisterWindowMessage(......);

  2. 程序Y必须具有处理自定义消息的代码 使用SetWindowLong或任何等效的

  3. 程序X,使用FindWindow查看程序Y是否正在运行

  4. 程序X,如果Y正在运行,请使用一致的文件名(例如XXYY1234(XXYYn)格式)在两端的已知位置/目录上创建临时文件,其中1234 = n

  5. 程序X,使用BroadcastSystemMessage向程序Y发送信号   NotifyRecipients:= BSM_APPLICATIONS; BroadcastSystemMessage(BSF_IGNORECURRENTTASK 或BSF_POSTMESSAGE或BSF_FORCEIFHUNG,@ NotifyRecipients,MsgAppNotifyID,任何自定义值,n);

  6. 编程y,使用WParam作为n处理上述消息,并重新构建进程文件

  7. 干杯 祝你好运

答案 8 :(得分:0)

运行一个只在连接上(作为触发器)的TCP服务器非常快,应该做你想做的事情