3. Paşii pentru implementarea în java a obiectelor
distribuite
Scopul lucrării de faţă este de a prezenta arhitectura
tehnologie Remote Server Invocation şi modul de utilizare pentru a implementa
aplicaţii distribuite.
Tehnologia RMI (Remote Method Invocation) este una dintre componentele
fundamentale ale java, fiind introdusă începând cu versiunea jdk 1.1 . Cu
ajutorul RMI părţi ale unui program pot exista pe maşini diferite
, acest lucru fiind transparent din puct de vedere al programului. RMI este una
dintre componentele fundamentale care stă şi la baza Enterprise Java Beans.
Programarea distribuită este benefică în foarte multe
situaţii. Pe măsură ce aplicaţiile cresc în dimensiuni,
complexitate şi resurse necesare, programarea într-un mediu distribuit
duce la creşterea performanţelor şi posibilităţilor de
administrare a acestor aplicaţii.
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.
Figura 1. Relatia
client server
Principiul de funcţionare al mecanismului RMI este
următorul: obiectele distribuite (accesibile de la distanţă) vor
trebui să implementeze interfaţa Remote. Fiecărui obiect distribuit
i se va asocia un nume şi va fi înregistrat de către 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.
Figura 2. Aplicaţie RMI.
În figura 2 sunt reprezentate toate componentele implicate
în cadrul unei aplicaţii distribuite ce foloseşte tehnologia RMI.
Aplicaţia server
Se defineşte interfaţa pe care o implementează obiectele
distribuite. Această interfaţă va conţine metodele ce vor
putea fi apelate de la distanţă prin intermediul mecanismelor RMI.
Interfaţa va trebui să extindă interfaţa RemoteInterface. Meotdele definite în
cadrul aceste interfeţe vor trebui sa aibă adăugată clauza throws RemoteException.
Construirea clasei ce implementează interfaţa definită la
pasul anterior. Această clasă trebuie să extindă clasa UnicastRemoteObject.
Pe baza clasei definite la pasul anterior pot fi construite
instanţe de obiecte ce vor putea fi accesate de la distanţa. Pentru a
putea face accesibil de la distanţă un obiect prin intermediul
mecanismului RMI trebuiesc realizaţi următorii paşi
suplimentari:
a)
Instalarea
în cadrul aplicaţiei ce urmează a construi şi înregistra obiecte
distribuite a unui manager de securitate folosind clasa java.rmi.RMISecurityManager.
b)
Lansarea
în execuţie fie din consolă, fie direct din cadrul aplicaţiei a
utilitarului rmiregistry. Lansarea de
la consolă se face executând comanda rmiregistry
port (unde port reprezintă portul pe care utilitarul va aştepta
conexiuni – sau cereri de accesare a obiectelor distribuite). Lansarea din
cadrul aplicaţiei se face cu instrucţiunea LocalRegistry.createRegistry(port) (unde port are aceiaşi
semnificaţie ca şi în cazul lansării de la consolă).
Paşii pentru a construi şi iniţializa un obiect
distribuit sunt următorii:
a)
Construirea
obiectului pe baza clasei ce implementează interfaţa RemoteInterface
b)
Înainte
de a putea instanţia si a face vizibile în reţea obiecte distribuite,
pe baza clasei definite la pasul anterior trebuiesc generate clasele Stub şi Skeleton folosind utilitarul rmic.exe.
Cele două clase vor fi generate de către utilitarul rmic.exe pe baza clasei ce
defineşte structura obiectelor distribuite. Aceste două clase
implementează mecanismele de transmitere la distanţa a apelurilor de
metode şi a răspunsurilor metodelor şi vor trebui să se
regăsească şi în classpath-ul
aplicaţiei client ce încearcă să apeleze un obiect
distribuit.
c)
Înregistrarea
obiectului în cadrul rmiregistry pentru
a puea fi accesat de la distanţă – Naming.rebid(„//numecalc:port/numeodiec); - unde numecalc
reprezintă numele calculatorului unde se afla startat rmiregistry, port
reprezintă portul pe care este lansat în exectuţie rmiregistri şi numeobiect
reprezintă numele prin intermediul căruia obiectul este recunoscut în
cadrul rmiregistry şi va putea
fi invocat de la distanţă.
Aplicaţia client
Pentru ca o aplicaţie client să obţină o
referinţă către un obiect distribuit trebuie să instaleze un manager de securitate de tip java.rmi.RMISecurityManager.
După instalarea managerului de securitate clientul poate
obţine o referinţă către un obiect distribuit folosind o
instrucţiune de froma:
ObjectInterface
obj = Naming.lookup(„//host:port/name”);
Unde host reprezintă
numele calculatorului pe care obiectul
distribuit este înregistrat, port reprezintă
portul pe care utilitarul rmiregistry este
lansat in execuţie pe maşina server şi name reprezintă numele sub care obiectul a fost înregistrat în
cadrul serverului.
După obţinerea referinţei către obiectul distribuit,
aplicaţia client va putea manipula respectivul obiect exact în
acelaşi mod ca şi obiectele locale, fiind transparent faptul că
apelul metodelor din obiectul distribuit sunt de fapt transmise la
distanţă prin intermediul mecanismelor RMI.
Mecanismele interne care se ocupă cu apelarea la distanţă
a metodelor distribuite şi întoarcerea rezultatelor metodelor apelate sunt
implementate în cadrul claselor Stub şi
Skeleton ce sunt generate de
către compilatorul rmic.exe.
Datorită acestui fapt cele două clase trebuie să fie accesibile
în cadrul aplicaţiei client ce încearcă să apeleze un obiect
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 3 este
descrisă arhitectura generală a aplicaţiei.
Figura 3. Arhitectura generala aplicaţie
RMI
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 figura 4 este prezentată diagrama UML a claselor pentru
aplicaţia server.
Figura 4. Diagrama UML a claselor pentru
aplicaţia server
In cadrul aplicaţiei server clasa ControlersServer este responsabilă cu iniţializarea
mecanismelor RMI, pentru a putea înregistra obiecte distribuite (figura y).
if(System.getSecurityManager()==null){
System.setSecurityManager (new
RMISecurityManager ());
}
Registry
reg = LocateRegistry.createRegistry (rmiport);
Figura y. Secvenţa de cod pentru initializare
security manager şi rmiregistry
De asemenea 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 figura 5 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();}
}
}
Figura 5. Metoda run() din cadrul clasei
ControlEngine.
In figura 6 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();
}
}
Figura 6. Metoda setAlgorithm(ControlAlgorithm alg)
din cadrul clasei ControlEngine, apelabilă de la distanţă.
In figura 7 este
prezentată diagrama UML a claselor aplicaţiei client.
Figura 7. Diagrama UML a claselor pentru
aplicaţia 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 figura 8 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]));
Figura 8. Secvenţă de instrucţiuni
pentru setarea unui algoritm de control.
Importaţi în mediul Eclipse proiectul ce exemplifică
noţiunile prezentate în acest laborator (link
proiect). Aplicaţia pentru acest proiect este formată din
două proiecte: un proiect pentru aplicaţia server RMI şi un
proiect pentru aplicaţia client RMI.
Compilati si executati aplicatiile server si client RMI.
Compilare aplicatie server
rmic –v1.2 –classpath . lab.scd.rmi.server.RouteFinderEngine
Compilare aplicatie client
Lansare in executie aplicatie server.
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";
};
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
Lansare in executie aplicatie client.
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";
};
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
Exrciţiul 2
Construiţi o
aplicaţie RMI pe baza descrierii aplicaţiei date în cadrul
paragrafului Exemplu aplicaţie RMI.