Java 如何在RMI(客户端代码)中使用在服务器端代码中定义的事件?

Java 如何在RMI(客户端代码)中使用在服务器端代码中定义的事件?,java,events,rmi,Java,Events,Rmi,在RMI(客户端代码)中,如何使用服务器端代码中定义的事件 例如,以下服务器端代码定义了PropertyChangeSupport事件 如何在客户端实现它? package rmiservice.services.calculator; import java.beans.PropertyChangeSupport; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.server.UnicastRe

在RMI(客户端代码)中,如何使用服务器端代码中定义的事件

例如,以下服务器端代码定义了
PropertyChangeSupport
事件

如何在客户端实现它?

package rmiservice.services.calculator;

import java.beans.PropertyChangeSupport;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;
import java.util.Queue;

public class CalculatorService extends UnicastRemoteObject implements ICalculator {
private Queue<Integer> numbers = new LinkedList<Integer>();
private Integer result;
***private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);***


public CalculatorService() throws RemoteException {
    super();

}


public void start() throws Exception {
    java.rmi.registry.LocateRegistry.createRegistry(1099);
    Naming.bind("CalculatorService", this);
    System.out.println("Calculator Service is Run  . . .");
}

public void stop() throws Exception {

    Naming.unbind("CalculatorService");
    UnicastRemoteObject.unexportObject(this, true);

    System.out.println("Calculator Service is Stop . . .");

}

//-------------------------------------------------------------------
//------------------------------ Implements ICalculator -------------

public void addNumber(Integer number) throws Exception {
    numbers.add(number);
}

public Integer getResult() throws Exception {
    return this.result;
}

public void setResult(Integer result) throws Exception {
    Integer oldResult = this.getResult();
    this.result = result;
    ***propertyChangeSupport.firePropertyChange("result", oldResult, result);***
}

public void calculate(Operation operation) throws Exception {
    Integer _result = 0;

    if (numbers.size() < 2)
        return;

    switch (operation) {
        case Add: {
            _result = 0;
            while (numbers.size() > 0) {
                _result += numbers.poll();
            }
            break;
        }

        case Substract: {
            _result = numbers.poll();
            while (numbers.size() > 0) {
                _result -= numbers.poll();
            }
            break;
        }

    }

    this.setResult(_result);

}
//-------------------------------------------------------------------
包rmiservice.services.calculator;
导入java.beans.PropertyChangeSupport;
导入java.rmi.Naming;
导入java.rmi.RemoteException;
导入java.rmi.server.UnicastRemoteObject;
导入java.util.LinkedList;
导入java.util.Queue;
公共类计算器服务扩展了UnicastRemoteObject实现了ICalculator{
私有队列号=新的LinkedList();
私有整数结果;
***私有属性更改支持属性更改支持=新属性更改支持(此)***
公共计算器服务()引发RemoteException{
超级();
}
public void start()引发异常{
java.rmi.registry.LocateRegistry.createRegistry(1099);
Naming.bind(“CalculatorService”,this);
System.out.println(“计算器服务正在运行…”);
}
public void stop()引发异常{
命名。解除绑定(“计算器服务”);
unexportObject(this,true);
System.out.println(“计算器服务停止…”);
}
//-------------------------------------------------------------------
//------------------------------电子计算器-------------
public void addNumber(整数)引发异常{
数字。添加(数字);
}
公共整数getResult()引发异常{
返回此结果;
}
public void setResult(整数结果)引发异常{
整数oldResult=this.getResult();
this.result=结果;
***propertyChangeSupport.firePropertyChange(“结果”,旧结果,结果)***
}
公共无效计算(操作)引发异常{
整数_结果=0;
if(number.size()<2)
返回;
开关(操作){
案例补充:{
_结果=0;
while(numbers.size()>0){
_结果+=数字。poll();
}
打破
}
案例摘要:{
_结果=数字。poll();
while(numbers.size()>0){
_结果-=number.poll();
}
打破
}
}
这个.setResult(_result);
}
//-------------------------------------------------------------------

}我可能误解了这个问题

但据我所知,RMI并不参与你所问的那种事件绑定

客户端基本上会查找在RMI注册表中注册的对象,并调用其中的方法

您必须自己实现事件处理代码

如果您询问如何调用RMI绑定对象的方法,您可以在


RMI不支持通知。但是,您可以使用带有事件支持的JMXbean,它可以在RMI上使用

为此,您的MBean接口必须进行扩展。

就是这样做的

很抱歉,我的示例已经制作好了,不能完全适合您的代码,但它应该很容易适应

1.共享代码(向任何客户端公开服务器接口的jar) 2.服务器代码(启动和接口实现) 因此:

  • 您的服务器通过共享jar公开其远程接口
  • 其中一个将在服务器端实现,以提供事件触发(这将是您的计算器服务)
  • 另一个将在客户端实现,以提供事件处理(我想这就是您的PropertyChangeSupport)
    • 客户端事件处理程序还必须是UnicastRemoteObject的子类,以便可以通过代理将其传递给服务器,并且服务器可以远程调用其上的方法
  • 回调应该异步调用,以防止一个客户端阻塞服务器线程和/或延迟对其他客户端的回调

如果在服务器端更改了属性值,如何将更改通知客户端?如果您仅使用RMI,唯一的选择是不断轮询,或者在客户端中有另一个RMI注册表。。。但这太麻烦了…他不需要在客户端注册。客户端可以向服务器注册回调。仅使用RMI?能给我指一个关于这个的资源吗??我不知道这是可能的…更不用说在谷歌上搜索并找到了。。如果在服务器端更改了属性值,如何将更改通知客户机?正如我提到的,您必须使用通知。您可以从Oracle站点了解更多信息-如果在服务器端更改了属性值,如何将更改通知客户端?尝试查找RMI回调您不想这样做。开销是巨大的。考虑到网络开销和延迟,您需要使远程方法更粗粒度,使它们值得执行。它不必是
UnicastRemoteObject的子类。
它可以手动导出。我真的没有得到反对票:这是一个实际可行的解决方案,详细解释,而且没有解释为什么这会很糟糕!
//The interface that RMI will use to pass event handlers between client and server
public interface ServerEventHandler extends Remote {

    //This is not actually required (using it for testing)
    public void setId(int id) throws RemoteException;

    //This is not actually required (using it for testing)
    public int getId() throws RemoteException;

    // Here we use String as event type.
    // Could be any number of Serializable or Remote arguments
    public void handle(String message) throws RemoteException;

}

// A simple interface that will allow us to remotely (un)register event handlers
public interface ServerObjectWithCallback extends Remote {

    public void addServerEventHandler(ServerEventHandler handler) throws RemoteException;

    public void removeServerEventHandler(ServerEventHandler handler) throws RemoteException;

}
public class ServerObjectWithCallBackImpl implements ServerObjectWithCallback {

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss,SSS", Locale.ROOT);

    // A counter to automatically assign unique ids to new event handlers
    private int handCount = 0;
    // This will provide references to client-side event handlers and a way to
    //  access their ids with no remote call
    private HashMap<Integer, ServerEventHandler> handlers = new LinkedHashMap<Integer, ServerEventHandler>();
    // A fixed pool of 10 threads for asynchronous event handling
    private ExecutorService threads = Executors.newFixedThreadPool(10);

    // A simple counter that will allow printing a different message each time
    // (for the sake of this test only)
    private int eventcounter = 0;

    @Override
    public synchronized void addServerEventHandler(ServerEventHandler handler) throws RemoteException {
        // Assign a new id to handler and keep a reference to it
        handler.setId(++handCount);
        handlers.put(handCount, handler);
        System.out.println("New handler added with id " + handCount);
    }

    @Override
    public synchronized void removeServerEventHandler(ServerEventHandler handler) throws RemoteException {
        try {
            // Get handler id and forget about it
            int id = handler.getId();
            handlers.remove(id);
            System.out.println("Handler with id " + id + " removed");
        } catch (RemoteException e) {
            System.err.println("#Could not retrieve id for handler to unregister");
        }
        // TODO safer method "removeById" that will avoid unnecessary remote call to getId()
    }

    public synchronized void onServerEvent(Object event) {
        // This is where the remote callbacks take place
        // This method is called from server side and performs asynchronous
        //      callback on each registered client
        // TODO event should actually be of a more meaningfull type than Object
        System.out.println(sdf.format(new Date()) + "> Firing event #" + ++eventcounter + ": " + event + " (" + handlers.size()
                + " registered handlers)");
        for (int id : handlers.keySet()) {
            try {
                ServerEventHandler handler = handlers.get(id);
                threads.execute(new EventRunnable(handler, id, event, eventcounter));
            } catch (Exception e) {
                System.err.println("#Could not execute async callback on handler " + id);
                e.printStackTrace();
            }
        }
    }

    // A private runnable that will suit our needs to perform callbacks asynchronously
    // If we didn't, server might hang because of client behavior or missing client
    // Moreover, one client being slow would delay event dispatch to other clients
    private static class EventRunnable implements Runnable {

        private ServerEventHandler handler;
        private int handlerId;
        private Object event;
        private int eventNum;

        public EventRunnable(ServerEventHandler handler, int handlerId, Object event, int eventNum) {
            this.handler = handler;
            this.handlerId = handlerId;
            this.event = event;
            this.eventNum = eventNum;
        }

        @Override
        public void run() {
            try {
                handler.handle("message #" + eventNum + " sent on " + sdf.format(new Date()) + " = " + event);
            } catch (Exception e) {
                // TODO Better exception management : react depending on cause
                System.err.println("handler " + handlerId + " seems to have gone away: " + e.toString());
                // TODO Self-unregister handler after some unavailability time
                //  and possibly destroy client session as well
            }
        }
    }

}

public class MainCallback {
    private static ServerObjectWithCallBackImpl soc;
    private static ServerObjectWithCallback stub;

    public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException {

        Registry reg = null;
        try {
            // Startup RMI registry
            reg = LocateRegistry.createRegistry(1099);
            System.out.println("RMI started");

            // Instantiate the RMI entry-point for the client, which will also
            //      be the object sending events
            soc = new ServerObjectWithCallBackImpl();
            stub = (ServerObjectWithCallback) UnicastRemoteObject.exportObject(soc, 0);
            // Bind the remote object's stub in the registry
            reg.bind("CallbackServer", stub);
            System.out.println("ServerObjectWithCallback bound to RMI (CallbackServer). Waiting for client");

            // This will be our event object : a counter
            int count = 0;
            while (true) {
                // Wait between 1 and 5 seconds
                Thread.sleep((int) (Math.random() * 4000 + 1000));
                // Fire event
                soc.onServerEvent(++count);
            }
        } finally {
            try {
                // Close up registry
                UnicastRemoteObject.unexportObject(reg, true);
                System.out.println("RMI registry destroyed");
            } catch (Exception e) {
                System.out.println("Could not destroy RMI registry");
            }
        }
    }
}
// This is our event handler implementation
// Note how it extends UnicastRemoteObject:
//  this is what allows the magic of calling client methods from server,
//  along with the fact that it implements ServerEventHandler, which is a Remote interface known from server
public class ClientSideEventHandler extends UnicastRemoteObject implements ServerEventHandler {

    private static final long serialVersionUID = 5094195049935134358L;

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss,SSS", Locale.ROOT);

    // id is just a way of discriminating clients for this test
    private int id;

    public ClientSideEventHandler() throws RemoteException {
        super();
    }

    // Make id available to server so it can number clients as it wishes
    @Override
    public int getId() throws RemoteException {
        return id;
    }

    @Override
    public void setId(int id) throws RemoteException {
        this.id = id;
    }

    // This is the actual callback method
    @Override
    public void handle(String message) throws RemoteException {
        System.out.println(sdf.format(new Date()) + "> Message from server: " + message);
    }

    // Overriding toString allows testing whether the handler is a reference or
    //      a serialized copy on server side
    @Override
    public String toString() {
        return this.getClass().getSimpleName() + "[" + id + "]";
    }

}

public class MainCallback {

    public static void main(String[] args) throws InterruptedException, RemoteException {
        // Connect to RMI registry on server
        Registry registry = null;
        try {
            registry = LocateRegistry.getRegistry("localhost", 1099);
            System.out.println("Connected to server");
        } catch (RemoteException e) {
            System.out.println("Error connecting to RMI");
            e.printStackTrace();
            return;
        }

        ServerObjectWithCallback soc = null;
        // Create an event handler on our side
        ClientSideEventHandler handler = new ClientSideEventHandler();
        try {
            // Get RMI server entry-point from remote RMI registry
            soc = (ServerObjectWithCallback) registry.lookup("CallbackServer");
            System.out.println("CallbackServer recovered from server");
            // Register for server events
            soc.addServerEventHandler(handler);
            System.out.println("Handler registered. Waiting for events");
        } catch (RemoteException | NotBoundException e) {
            System.out.println("Error getting MyRemoteInterface");
            e.printStackTrace();
            return;
        }

        // Just wait indefinitely for an event to happen
        while (true) {
            Thread.sleep(1000);
        }
    }

}