1

我有一个 RMI 服务器,许多客户端连接到该服务器。当客户端注册更改时,服务器会做出反应并指示所有客户端进行更改。

我一直在研究许多 hello world RMI 示例,但没有一个解决如何从服务器持久连接回客户端。

我想要实现的是这些方面:服务器在 rmiregistry 上注册。客户端 1 连接到服务器并调用服务器上的方法。客户端 2 连接到服务器并调用服务器上的方法。服务器将更改从 client2 发送到 client1。

如果不将每个客户端都注册为服务器,如何实现这一点?

编辑:看了几个优秀的答案(更平坦)后,我遇到了以下异常 java.rmi.StubNotFoundException: Stub class not found: thegame.connectivity.GameClient_Stub; 嵌套异常是:

java.lang.ClassNotFoundException: thegame.connectivity.GameClient_Stub
    at sun.rmi.server.Util.createStub(Util.java:292)
    at sun.rmi.server.Util.createProxy(Util.java:140)
    at sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:196)
    at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:310)
    at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:237)
    at thegame.connectivity.GameClient.bind(GameClient.java:37)
    at thegame.connectivity.GameClient.run(GameClient.java:51)
    at thegame.connectivity.GameClient.main(GameClient.java:29)
Caused by: java.lang.ClassNotFoundException: thegame.connectivity.GameClient_Stub
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at sun.rmi.server.Util.createStub(Util.java:286)
    ... 7 more

在这堂课上

public class GameClient extends Thread implements Remote, Client, ModelChangeListener<Client>{
    private static final long serialVersionUID = -394039736555035873L;
    protected Queue<GameModelEvent> queue = new ConcurrentLinkedQueue<GameModelEvent>(); 


    public GameClient(){

    }

    public static void main(String[] args){
        GameClient client = new GameClient();
        client.run();
    }


    protected void bind(){
        System.setProperty("java.rmi.server.codebase","file:bin/");
        try {
            Registry registry = LocateRegistry.getRegistry();
            Client c = (Client)UnicastRemoteObject.exportObject(this);
            Server stub = (Server) registry.lookup("Server");
            stub.registerClient(c);
        } catch (RemoteException | NotBoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }



    @Override
    public void run() {
        super.run();
        bind();
        while(!Thread.interrupted()){
            System.out.print(".");
            GameModelEvent event = queue.poll();
            while(event != null){

                System.out.println(event);


                event = queue.poll();
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return;
            }
        }

    }
...

当我注释掉 UnicatRemoteObject 并仅将 null 作为参数传递时,它运行良好。所以有一个连接,一切都在运行。但是没有存根......注释掉代码库也没有效果。代码库在服务器上运行良好。

关于什么是错的任何想法?

4

4 回答 4

2

当您的客户第一次联系 RMI 服务器时,他们可以提供某种类型的侦听器来实现该Remote接口。服务器可以维护这些RemoteListeners 的集合,并且无论何时应通知客户端某些事件,它都可以调用remoteAction(ActionEvent e)您想要创建的任何内容。如果不了解您的问题域的更多信息,很难具体说明。

于 2013-01-21T13:15:13.713 回答
1

客户端不仅可以与服务器对话,还可以直接相互对话。

在客户端上注册客户端,从少数“主客户端”开始构建类似树状结构的东西,并且只有这些客户端向服务器注册。它们可以将消息级联到其他子客户端,因此可能支持大量客户端,而只有少数直接在服务器上注册。

不幸的是,使用这种方法必须允许客户端正确关闭,以便他们可以将当前拥有的子客户端的职责分配给其他节点,或返回服务器。他们不能简单地被杀死,因为整个树枝都会被切断。也许如果客户端感觉与主服务器不再连接,它应该向服务器请求并获取另一个。

另请参阅可以完成的文档

也不要调用exportObject(Remote obj),因为使用它需要有一个存根类(它返回该类的一个实例,RemoteStub)。使用exportObject(Remote obj, int port)返回接口。也盖在这里

于 2013-01-21T13:24:58.360 回答
1

这可以使用 RMI 回调来实现。很多细节和源代码可在http://docs.oracle.com/cd/E13211_01/wle/rmi/callbak.htm#1023775 获得

于 2013-01-24T12:53:33.120 回答
0

回答第一部分)

要使用纯 RMI 执行此操作,客户端必须充当 RMI 服务器以用于回调 - 根本无法摆脱这一点,这就是 RMI 协议的工作方式。

如果您不希望每个客户端都以这种方式运行,那么您将需要在网络堆栈中降低一个级别并实现您自己的套接字协议来代替 RMI。当服务器接收到注册消息时,它需要保持客户端建立的套接字连接,根据需要通过该套接字将数据推送回客户端。

回答第二部分(在对问题进行编辑之后))

在使用时导出客户端时,您需要预先生成一个存根,UnicastRemoteObject.exportObject(Remote)如 JDK 源中的以下注释所示:

使用 UnicastServerRef 构造函数传递布尔值 true 以指示应仅使用生成的存根类。必须使用生成的存根类而不是动态代理,因为此方法的返回值是动态代理类无法扩展的 RemoteStub。

因此java.lang.ClassNotFoundException.

好吧,要么那个要么打电话UnicastRemoteObject.exportObject(Remote, int)

您还需要通过调用指定服务名称来将存根绑定Registry.bind(String, Remote)到注册表。

这实际上是将客户端作为 RMI 服务器运行 - 正如我在回答“答案第一部分”中所说,这是使用 RMI 时可以工作的唯一方法 - RMI 在那里进行远程方法调用,仅此而已。

于 2013-01-21T13:18:04.910 回答