Invocarea metodelor la distanta - RMI

Remote Method Invocation (RMI) este o tehnologie Java prin intermediul careia poate fi invocat aflat la distanta pe o alta masina virtuala (pe acelasi calculator sau pe un alt calculator accesibil in retea).

RMI utilizeaza in spate in mod intensiv comunicarea prin socket-uri si serealizarea si deserializare obiectelor. RMI adauga un nivel de abstractizare in plus peste comunicarea prin socket-uri astfel incat programatorul nu trebuie sa lucreze in mod direct cu conexiuni, socket-uri si fluxuri de intrare/iesire deoarece toate aceste detalii sunt ascunse.

O aplicaţie RMI poate fi privită ca fiind compusă din două programe : un program client şi un program server. Programul server crează unul sau mai multe obiecte distribuite, şi aşteaptă programele client care vor invoca metodele acestor obiecte distribuite.

Aplicaţiile ce folosesc RMI mai sunt numite şi aplicaţii cu obiecte distribuite. Într-o astfel de aplicaţie pot fi identificate următoarele activităţi ce au loc:

  • Localizarea obiectelor distribuite – aplicaţia foloseşte diverse mecanisme pentru a localiza obiectele distribuite şi pentru a obţine referinţe către acestea;
  • Comunicarea cu obiectele distribuite – detaliile de comunicare cu obiectele distribuite sunt gestionate de mecanismele interne ale RMI;
  • Încărcarea definiţii claselor – deoarece tehnologia RMI permite transmiterea obiectelor prin reţea, se oferă mecanisme de încărcare a claselor şi de transmitere a obiectelor prin reţea.

In figura de mai jos este prezentată o diagramă simplă ce ilustrează relaţia dintre client şi server într-o aplicaţie distribuita RMI.

Principiul de funcţionare al mecanismului RMI este următorul:

  • obiectele distribuite (accesibile de la distanţă) implementeze interfaţa Remote;
  • fiecărui obiect distribuit i se va asocia un nume şi va fi înregistrat in cadrul rmirgistry;
  • clientul (care trebuie să cunoască numele cu care a fost înregistrat obiectul în rmiregistry) va primi o referinţă către obiectul distribuit, după care, va lucra cu acesta ca şi cum acesta ar fi un obiect local.

Toate detaliile despre ceea ce se întâmplă de fapt în momentul în care se apelează o metoda a obiectelor distribuite, sunt invizibile pentru client şi pentru server, si sunt înglobate în cadrul claselor Stub şi Skeleton.

Aplicatie pentru controlul distribuit

Aplicaţia ilustrează modul în care tehnologia RMI poate fi folosită pentru a implementa un sistem care să permită controlarea de la distanţă a unor resurse. In cazul de faţă sa implementat o aplicaţie ce permite modificarea de la distanţă a algoritmilor de control pentru un set de controlere. In figura de mai jos este descrisă arhitectura generală a aplicaţiei.

Aplicaţia este formată din două componente: componenta Control local care acţionează ca un server şi componenta Control la distanţă care acţionează ca şi un client.

Componenta server permite iniţializarea şi instalarea a unuia sau mai multor controlere. Prin intermediul mecanismului RMI aplicaţia server permite instalarea în cadrul controlerelor de la distanţă, de către aplicaţia client a algoritmilor de control pentru fiecare dintre controlerele instalate.

Componenta client defineşte unul sau mai mulţi algoritmi de control pe care îi poate instala în cadrul controlerelor aflate la distanţă prin intermediul mecanismului RMI.

Aplicaţia exemplifică modul în care mecanismul RMI permite transmiterea la distanţă de cod executabil.

In cadrul aplicaţiei server clasa ControlersServer este responsabilă cu iniţializarea mecanismelor RMI, pentru a putea înregistra obiecte distribuite.

    if(System.getSecurityManager()==null){
            System.setSecurityManager (new RMISecurityManager ());
    }       
    Registry  reg = LocateRegistry.createRegistry (rmiport);

Clasa ControlersServer defineşte metoda registreControler (ControlerEngine ctr) prin intermediul căreia un controler este înregistrat în sistem (folosind metoda Naming.rebind(…)) şi poate fi accesat de la distanţă, şi metoda unregistreControler(String ctrlName) prin intermediul căreia un controler poate fi şters din sistem (folosind metoda Naming.unbind(…)).

Clasa ControlEngine defineşte structura obiectelor ce vor putea fi apelate de la distanţă, ea implementând interfaţa Controler în cadrul căreia sunt declarate metodele accesibile de la distanţă. Această clasă este de tip fir de execuţie şi permite rularea în cadrul firului a unui algoritm de control. Setarea algoritmului de control se face prin intermediul metodei setAlgorithm(ControlAlgorithm alg) care poate fi apelată de la distanţă de către clienţi distribuiţi. Clientul are posibilitatea de a defini proprii algoritmi de control, şi de a-i instala în cadrul controlerului, el trebuind să apeleze la distanţă metoda setAlgorithm(ControlAlgorithm alg) transmiţând ca parametru al metodei un obiect ce implementează interfaţa ControlAlgorithm.

In secventa de mai jos este prezentată structura metodei run() ce se execută în cadrul firelor de tip ControlerEngine.

public void run(){
        nextState = RUNNING;
 
        while(active){
            synchronized(look){
                algorithm.executeStep();
                if(nextState == PAUSED)
                    try {
                        look.notify();
                        look.wait();
                        nextState = RUNNING;
                    } catch (InterruptedException e1) {                  
                        e1.printStackTrace();
                    }
            }
            try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}
        }
      }

In secventa de mai jos este prezentă metoda setAlgorithm(ControlAlgorithm alg) apelabilă de la distanţă de către clienţi distribuiţi prin intermediul mecanismului RMI.

public void setAlgorithm(ControlAlgorithm alg) {
        if(controlerThread==null){
            controlerThread = new Thread(this);
            controlerThread.start();
        }
        else{
            nextState = PAUSED;     
                    synchronized(look){
                        try {
                            look.wait();                             
                        } catch (InterruptedException e) {               
                            e.printStackTrace();
                        }
                    }
 
        }//.else
 
        synchronized(look){
            this.algorithm = alg;
            nextState = RUNNING;
            look.notify();
        }
    }

In figura de mai jos este prezentată diagrama UML a claselor aplicaţiei client.

Aplicaţia client implementează unul sau mai mulţi algoritmi de control pe care îi poate încărca în cadrul controlerelor ce rulează în alte locaţii. Comunicaţia între client şi controler se face prin intermediul mecanismului RMI, aplicaţia client putând obţine o referinţă a unui controler, pentru care apoi să seteze un alt algoritm de control prin apelarea metodei setAlogirthm(ControlAlgorithm).

In listingul urmator este prezentate secvenţele de instrucţiuni necesare pentru un client pentru a seta un algoritm pe n controler distribuit.

if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
 }
Controler ctrl = (Controler)Naming.lookup(remoteObjectName);
ctrl.setAlgorithm(new SimpleControlAlgorithm(args[1]));

Aplicatie RMI

Descarcati si rulati aplicatia: Aplicatie RMI

Observatie: Aceasta aplicatie nu este o implementare a specificatiilor din secitunea anterioara.

Aplicaţia este formată din două proiecte: un proiect pentru aplicaţia server RMI şi un proiect pentru aplicaţia client RMI.

Compilare aplicatie server

  • Compilati aplicatia server (direct din mediul Eclipse sau din linia de comanda).
  • Generarea fisierului stub folosind utilitarul rmic. Din directorul radacina al proiectului laborator4_rmi_server se executa comanda: rmic –v1.2 –classpath . lab.scd.rmi.server.RouteFinderEngine

Compilare aplicatie client

  • Compilati aplicatie client (direct din mediul Eclipse sau din linia de comanda).

Lansare in executie aplicatie server

Adaugati in directorul radacina al proiectului server fisierul cu numele java.policy cu urmatorul continut:

grant {
    permission java.net.SocketPermission "*:1024-65535",
        "connect,accept";
   permission java.io.FilePermission
        "c:\\java\\eclipse\\workspace\\laborator4_rmi_client\\-", "read";
    permission java.io.FilePermission
        "c:\\java\\eclipse\\workspace\\laborator4_rmi_server\\-", "read";
};

Adaugati in directorul radacina al proiectului fisierul startserver.bat cu urmatorul continut:

java -cp . -Djava.rmi.server.codebase=file:/c:\java\eclipse\workspace\laborator4_rmi_server/ -Djava.rmi.server.hostname=localhost -Djava.security.policy=java.policy lab.scd.rmi.server.RouteFinderEngine

Lansati in executie aplicatia server executand startserver.bat

Lansare in executie aplicatie client

Adaugati in directorul radacina al proiectului client fisierul java.policy cu urmatorul continut:

grant {
    permission java.net.SocketPermission "*:1024-65535",
        "connect,accept";
    permission java.io.FilePermission
        "c:\\java\\eclipse\\workspace\\laborator4_rmi_client\\-", "read";
    permission java.io.FilePermission
        "c:\\java\\eclipse\\workspace\\laborator4_rmi_server\\-", "read";
};

Adaugati in directorul radacina al proiectului fisierul startclient.bat cu urmatorul continut:

java -cp . -Djava.rmi.server.codebase=file:/c:\java\eclipse\workspace\laborator4_rmi_server/ -Djava.security.policy=java.policy lab.scd.rmi.client.RmiClient localhost

Lansati in executie aplicatia client executand startclient.bat