как можно использовать в RMI (код на стороне клиента) событие, которое определяется в коде на стороне сервера?

В 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);

}
//-------------------------------------------------------------------

}


person user2049371    schedule 12.02.2013    source источник
comment
Если значение свойства изменяется на стороне сервера, как клиент может быть проинформирован об изменении?   -  person user2049371    schedule 12.02.2013
comment
Попробуйте найти обратные вызовы RMI   -  person MadProgrammer    schedule 12.02.2013
comment
Вы не хотите этого делать. Накладные расходы огромны. Вам нужно сделать ваши удаленные методы более грубыми, чтобы их можно было выполнять с учетом сетевых накладных расходов и задержек.   -  person user207421    schedule 11.09.2014


Ответы (3)


RMI не поддерживает уведомление. Но вы можете использовать JMX Beans с поддержкой событий, которые можно использовать поверх RMI.

Для этого ваш интерфейс MBean должен расширять NotificationEmitter. .

person R Kaja Mohideen    schedule 12.02.2013
comment
Если значение свойства изменяется на стороне сервера, как клиент может быть проинформирован об изменении? - person user2049371; 12.02.2013
comment
Как я уже говорил, вы должны использовать уведомления. Подробнее об этом можно прочитать на сайте Oracle — docs.oracle.com/ javase/tutorial/jmx/notifs/index.html - person R Kaja Mohideen; 12.02.2013

Вот как это делается

Извините, мой пример уже сделан и не будет точно соответствовать вашему коду, но его должно быть довольно легко адаптировать

1. Общий код (банка, открывающая интерфейсы сервера любому клиенту)

//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;

}

2. Серверный код (запуск и реализация интерфейса)

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");
            }
        }
    }
}

3. Клиентский код (запуск и реализация обработчика)

// 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);
        }
    }

}

So:

  • Ваш сервер предоставляет свои удаленные интерфейсы через общую банку
  • Один из них будет реализован на стороне сервера для запуска событий (это будет ваш CalculatorService).
  • The other one will be implemented on client side to provide event handling (that would be your PropertyChangeSupport I guess)
    • The client-side event handler also has to be a subclass of UnicastRemoteObject, so that it can be passed by proxy to the server, and that the server can remotely call methods back on it.
  • Обратные вызовы следует вызывать асинхронно, чтобы один клиент не блокировал поток сервера и/или не задерживал обратные вызовы другим клиентам.
person julien.giband    schedule 10.04.2014
comment
Он не обязательно должен быть подклассом UnicastRemoteObject.. Его можно экспортировать вручную. - person user207421; 12.09.2014
comment
Я действительно не получаю отрицательных голосов: это действительно работающее решение, подробно объясненное, и не дается никаких объяснений, почему это было бы плохо! - person julien.giband; 15.03.2016

Я, возможно, неправильно понял вопрос.

Но, насколько я знаю, RMI не участвует в привязке событий, о которой вы спрашиваете.

Клиент в основном ищет объекты, зарегистрированные в реестре RMI, и вызывает в нем методы.

Вам придется реализовать код обработки событий самостоятельно.

Если вы спрашиваете, как вызвать методы привязанного объекта RMI, вы можете найти это в

http://en.wikipedia.org/wiki/Java_remote_method_invocation

person Thihara    schedule 12.02.2013
comment
Если значение свойства изменяется на стороне сервера, как клиент может быть проинформирован об изменении? - person user2049371; 12.02.2013
comment
Единственный вариант, если вы используете только RMI, - это постоянно опрашивать или иметь другой реестр RMI в клиенте... Но это грязно... - person Thihara; 12.02.2013
comment
Ему не нужен реестр в клиенте. Клиент может зарегистрировать обратный вызов на сервере. - person user207421; 13.02.2013
comment
Только с RMI? Можете указать мне ресурс об этом ?? я не знала, что так можно.... - person Thihara; 13.02.2013
comment
Неважно, погуглил и нашел. /E13211_01/wle/rmi/callbak.htm - person Thihara; 13.02.2013