C++和Java之间的低延迟IPC

14

如何在以下情况下实现C++/Java IPC的最佳方法?

(最近有人提出了一个类似的问题,但我的要求更具体)

  1. 我有两个程序 - 一个用C++编写,另一个用Java编写 - 需要相互通信。两者都在同一台机器上运行。

  2. 这些程序发送消息给对方。消息通常很短(少于几百字节),但可能会达到100KB或更大。

  3. 消息不需要得到确认(即不是像HTTP那样的请求/响应模型)。例如,C++程序向Java程序发送消息,Java程序可以稍后通过向C++程序发送消息来回复 - 反之亦然。

  4. 理想的解决方案将具有a)非常低的延迟,b)没有安全麻烦(用户不必授权打开端口等)和c)将是平台无关的。

我的第一个想法是使用套接字 - 每个程序都充当对方的服务器。套接字比其他形式的IPC具有更多的开销,并且如果我让系统自动分配端口号,我不知道服务器将如何通知客户端端口号。我还考虑过命名管道,但它们在不同平台上没有得到支持(至少不是一致的)。JNI看起来是一个选择,但它能跨进程边界吗?

有什么建议吗?

谢谢!

后续问题

  1. 如果我使用sockets,是否需要打开两个sockets以允许如上所述的异步通信?

回答您的后续问题 - 不,两种语言都支持异步IO。 - Nim
6个回答

11

我建议您使用TCP sockets

根据我的经验,TCP sockets的实际开销与应用程序的其他任务相比非常低,至少在我所使用的开发过程中是如此。我的意思是,即使套接字的延迟是其他IPC机制的两倍,它们在整个工作流程中的影响也很小。而且,使用TCP sockets可以避免在Java应用程序和C++应用程序之间进行IPC的麻烦,因为这最终会要求您使用一个特定的Java库,该库使用JNI,具有JNI和库本身的开销。

实际上,在我的Java应用程序中,我测量出垃圾收集器的影响比“loopback”TCP sockets引起的延迟更重要。

此外,TCP sockets比传统IPC更具可扩展性(和可移植性!)。如果未来您需要在不同的计算机上运行客户端和服务器呢?在“TCP sockets”场景中,您只需要进行5分钟的hack,而在“传统IPC”场景中,您将不得不重新编写整个IPC系统。

然而,您的应用程序的一般工作流程是什么?

即使不需要确认,我建议使用TCP(而不是UDP)以避免无序传递(这会导致在重新排列所接收到的内容时出现麻烦 - 您的某些消息为100KB,这无法适配UDP数据包)。

针对您最后一个问题,要使服务器通知客户端端口号,您可以让服务器使用特定的'port'命令行参数启动客户端,或者让服务器在/tmp(或其他临时目录)下保存一个带有端口号的小文件。


谢谢,TCP套接字听起来是正确的选择。我能建立一个套接字(将两个程序中的一个指定为“服务器”),并保持连接长时间运行,只要两个程序都在运行,这会带来任何问题吗? - Tony the Pony
1
@Jen:你说过你不想让用户需要“允许”使用端口。我很确定,即使是在OS X上,如果两个Java进程想要使用TCP套接字进行通信,一个默认的OS X 10.5+安装会触发一个弹窗询问是否允许程序“打开”端口。即使只是在两个本地进程之间。 - SyntaxT3rr0r
@SyntaxT3rr0r:说得好。我假设这个问题只会被问一次,然后程序就被视为“已授权”。如果不是这样的话,有没有办法避免用户每次都需要明确授权? - Tony the Pony
1
Windows防火墙不会弹出循环监听套接字窗口。我不知道在OS X上是否也是如此(据我所知,防火墙在该系统上默认是禁用的)。回答您的另一个问题,是的,您可以保持TCP连接长时间开启,但请注意服务器监听的是环回接口,而不是INADDR_ANY。 - gd1
对于服务器通知客户端端口,您可以通过在服务器上启动带有特定“端口”命令行参数的客户端来实现。这是一个好主意。 - BAR

6

我听说过关于这种情况的好事,特别是ZeroMQ。它甚至在某些情况下比TCP更快。简而言之,尝试一下肯定不会有坏处。


5
一种替代方案是使用内存映射文件,并通过检查编译器设置来保持其可移植性,判断是否为POSIX。POSIX操作系统具有“mmap()”函数,在Windows中则需要使用“CreateFileMapping()”函数。
在Boost库中有一个C++的可移植实现,在Java中您可以使用“FileChannel()”函数。
这个页面很好地解释了如何将其用于IPC:http://en.wikipedia.org/wiki/Memory-mapped_file

1
+1个好的提示,但是在实现它时可能会变得相当复杂(随后需要进行准确的测试和调试),以获得几乎没有性能提升。此外,当需要将应用程序移植到适当的“分布式应用程序”时,必须重写IPC部分,而使用sockets则不必这样做。[可扩展性] - gd1
1
是的,但我们将其用于Java图像生成器和C++控制器之间的通信。文件大小有点大,对我们来说在单向通信中是有意义的。由于不需要确认,因此制作两个内存映射文件应该不太难,其中一个是Java到C++,另一个是C++到Java。 - Wouter Simons
正确。我们应该更了解提问者的应用程序。 - gd1

4
当你说“极低延迟”时,需要有所限定。你可以通过Socket回路发送消息,并在20微秒内获得往返时间(RTT)。如果这已经足够快了,那么我会选择这种方案。
如果这速度还不够快,我会将C++代码放入Java应用程序中,并通过JNI调用它。这样可以获得约30纳秒的RTT。
使用内存映射数据的问题在于确保正确的互锁。你可能会发现某种解决方案在一个系统上有效,但在其他系统上可能无法正常工作。

1
+1 很好的观点。我可以再补充一点:如果处理消息所需的时间(即生成答案)为5毫秒,则套接字的开销为0.4%。我真的很想看到这个应用程序的一些基准测试... - gd1

2
使用JNI可以访问系统中所有可能性,而不仅仅是Java直接支持的内容;例如,如果使用共享内存,则需要使用JNI。 但是,JNI本身相当昂贵。
延迟的问题很棘手,因为我所知道的机制都没有任何保证。总的来说,最快的可能是某种形式的共享内存,在数据存在时使用信号来唤醒另一个进程。这将要求在Java端使用JNI,但是正确地完成,可能仍然会提供最低的延迟 - 然而要正确地完成(确保没有消息丢失)可能远非易事。基于Unix平台支持排队信号,并在单独的线程中处理它们作为事件;我不知道Windows是否支持。
除此之外,命名管道通常相当有效;延迟可以和共享内存一样好,但传递数据需要更长时间(因为必须通过系统进行复制)。并且应该可以直接从Java中访问它,而无需使用JNI。在Unix下,也可以配置套接字以快速响应(实际上,这就是命名管道在幕后的工作原理);然而,我不知道Java接口是否支持这些配置选项,也不知道它们是否在Windows下可用。

1

另一种选择是使用嵌入式数据库(因为您考虑了多个IPC,我假设两个应用程序在同一台机器上)。

我曾经在一个应用程序上工作过,在那里C++应用程序从各种渠道获取数据并将其放入数据库中(内存数据库;TimesTen)。为了向用户显示这些数据,Java应用程序会从数据库中查询它。

对于您的用途,我不知道您是否愿意考虑Oracle的Timesten,但您也可以使用Berkeley的嵌入式数据库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接