两个独立的Java桌面应用程序之间的通信

53

我希望开发两个独立但相关的Java桌面应用程序。

我希望一个应用程序能够触发另一个应用程序,传递数据并可以编辑后传回,即通信是双向的。如果另一个应用程序已经运行,则希望它们之间直接通信,即不仅仅通过命令行传递参数等。

一般来说,我应该寻找哪些策略/技术来实现这一点?


1
很好的问题。你曾经实现过吗? - Dan Rosenstark
还没有...这只是即将开始的项目的一些初步研究 :) - William
1
重新开放投票的人能表达他们的理由吗? - Passer By
我处于同样的情况,而且在思考,为什么不只使用CLI?有什么问题吗? - Lukas Lukac
12个回答

23

为了展示让两个应用程序互相通信是多么容易,可以查看使用 JGroups 的网络剪贴板演示。只需启动两个实例并开始将文件拖放到其中一个实例中,第二个实例将立即显示相同的文件。

import java.io.Serializable;
import java.awt.*;
import java.awt.datatransfer.*;
import javax.swing.*;
import org.jgroups.*;

public class JGroupsTest {

    public static void main(String[] args) throws Exception {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        frame.setSize(500, 300);
        final DefaultListModel listModel = new DefaultListModel();
        final JList panel = new JList(listModel);
        panel.setBackground(new Color(128, 0, 40));
        panel.setForeground(new Color(240, 240, 240));
        frame.add(panel);
        System.setProperty("java.net.preferIPv4Stack", "true");
        final JChannel channel = new JChannel("udp.xml");
        channel.connect("networkclipboard");
        channel.setReceiver(new ReceiverAdapter() {
            @Override
            public void viewAccepted(View newView) {
                frame.setTitle("Network Clipboard - " + channel.getLocalAddress());
            }

            @Override
            public void receive(Message msg) {
                listModel.addElement(msg.getObject());
            }
        });

        panel.setTransferHandler(new TransferHandler() {
            @Override
            public boolean importData(JComponent comp, Transferable t) {
                DataFlavor[] transferDataFlavors = t.getTransferDataFlavors();
                for (DataFlavor flavor : transferDataFlavors) {
                    try {
                        Object data = t.getTransferData(flavor);
                        if (data instanceof Serializable) {
                            Serializable serializable = (Serializable) data;
                            Message msg = new Message();
                            msg.setObject(serializable);
                            channel.send(msg);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return super.importData(comp, t);
            }

            @Override
            public boolean canImport(TransferSupport support) {
                return true;
            }

            @Override
            public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
                return true;
            }

        });
    }

}

嗨 @mhaller,我想知道你在 main 方法中使用 throws 的具体原因是什么,因为 main 方法是一个特定的方法,我们会用 try catch 包围它。请详细说明一下。 - Vishrant
@Vishrant - 我只是有点懒。在主方法中使用throws是允许的,不会有什么区别。我也经常为单元测试声明throws Exception,这样我就不必逐个处理所有已检查异常。 - mhaller
谢谢回复,我主要在像struts这样的容器中使用throws关键字,但是在我的应用程序中编写main方法(这是一个特定的方法而不是常见的方法)时,我使用try catch块以自己的方式处理Exception。是的,在main方法中使用throws会有所不同,如果main方法中发生任何Exception,它将被抛到JVM并且您的程序将停止,同时JVM以其自己的方式处理抛出的Exceptionthrows是“重新抛出异常”的概念,用于“常见方法”(许多类使用的方法)。 - Vishrant

22

这取决于您希望如何通信这两个程序:

  • 如果您只需要进程间信号量,可以在 /tmp 中的某个位置创建一个文件并锁定它。

  • 如果您只需要进程间同步消息传递(远程过程调用),RMI 应该是最容易的。

  • 如果您需要异步进程间消息传递,则 JMS 应该是最容易的。

  • 如果您需要进程间共享内存,则使用映射文件。

  • 如果您需要上述所有内容,Terracotta (http://www.terracotta.org/) 是最简单的方式:在同一台甚至不同计算机上的不同 JVM 上运行的 Java 程序彼此之间就好像在同一台机器上的一个 JVM 中执行一样。将一个程序分成几个甚至不需要进行任何代码更改-仅需编写 XML 配置文件。


19
他们可以分别监听一个Socket。这个教程很适合初学者。请注意,链接中的Socket是Java中的类名,不需要进行翻译。

这是通常的做法还是RMI?或者说,它们有什么不同? - Pacerier
@Pacerier - Sockets 是将字节流传输到网络堆栈的一种方式。 RMI 允许调用存在于不同 JVM 中的对象方法,数据交换对 RMI 不透明,但仍可在底层使用 sockets。 - Jé Queue
好的,那么套接字是最好的选择,因为它既简单又快速。 - Pacerier

10

你也应该考虑使用老牌的 RMI(远程方法调用)。


当我想要有两个不同的进程时,这通常是采用的方法吗? - Pacerier
这是在不同JVM之间(通常作为不同进程)传递Java对象的基本方式。您还可以共享对象引用以对其他JVM中的远程对象进行操作。当然,整个EJB和框架都试图抽象出RMI,但这是一种真正的共享多JVM共享对象的方法。公平地说,这是一种调用远程方法的方式,但大多数方法返回或传递对象。 - Jé Queue

6

看一下JavaGroups,它可以解决您的通信问题,并帮助您检测其他应用程序是否在运行。如果应用程序没有运行,则必须使用java.lang.Runtime.exec()启动一个新的JVM...


1
+1 for JGroups,我很喜欢它。顺便提一下,你知道这个页面已经存在10年了吗?同样的代码现在已被JBoss采用。请看jgroups.org。 - mhaller

4

尝试使用SocketCommunication进行通信,即使应用程序在同一台机器上也可以。

这里可以找到更多关于如何做到这一点的信息(Sun/Java文档)。


4
  • 采用“企业”方式运行这些应用程序将在Java EE服务器或至少Spring框架中运行。这可能是过度杀伤力的。

  • 如果需要通信大量数据,则可以使用RMI。

  • 如果您不害怕自己的协议、数据结构和错误处理,可以设置服务器和客户端套接字并通过它们进行通信。

  • 我认为,通过在共享目录中的文件或通过共享数据库进行通信的替代方案具有一定的粗糙吸引力(设置自己的协议以确定何时写入或删除文件),虽然不是非常快速,但非常简单可靠。而且从外部监视“通信”也相当容易。


2
为了简单起见,为什么不直接使用普通的TCP套接字呢?

普通的TCP套接字不定义协议,也不序列化对象或提供任何错误恢复功能,还有其他一些问题。你必须自己照顾好所有的东西。当然,RMI也不是很好! - Carl Smotricz
但是,如果数据很简单,处理起来就不是什么大问题,您也不必费心额外配置像RMI注册表这样的东西。 - Mark
@Mark,还有其他低层次的选择吗?或者说在同一台机器上传输数据,TCP/IP是唯一的方法吗? - Pacerier
@Pacerier - 同一台机器打开了许多选项,如共享内存、本地消息队列、文件、域套接字等等。在网络堆栈(例如TCP或UDP)之上放置协议可以轻松扩展操作系统实例的范围。 - Jé Queue

1

我赞同使用Socket通信和RMI。虽然RMI需要更多的操作,但对于程序员来说更直观。不过这取决于你要发送什么类型的信息。将原始字节推送到另一台机器可能比运行RMI服务器并处理所有相关问题更有意义...


1

这取决于您想在两个应用程序之间进行何种通信。例如,如果您使用套接字或RMI,那么两个应用程序都需要运行才能进行通信。如果您想要进行的通信类型可以更加异步,则可以使用更基于消息的方法。

例如,ZooKeeper允许您在非常简单而强大的原语之上实现几乎任何东西。此页面(http://hadoop.apache.org/zookeeper/docs/current/recipes.html)解释了如何使用ZooKeeper构建更高级别的结构。

缺点是您需要另一个系统。例如,如果您使用JGroups,那么就不需要。

希望这有所帮助。


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