11. Programowanie rozproszone:
identyfikacja zasobów (JNDI) i protokół RMI-IIOP
W tym punkcie poruszymy ważny temat lokalizacji zasobów w
środowiskach rozproszonych. Bardzo dobrze będzie widoczne jego
znaczenie przy omawianiu protokołu RMI-IIOP. Ten zaś jest podstawą
interakcji w serwerach J2EE.
1. Serwisy nazw i katalogów
Koncepcja serwisów nazw i katalogów (naming and
directory services) polega ogólnie na tym, aby w sposób
zunifikowany, niezależny od fizycznych właściwości danego zasobu i bez
konieczności znajomości jego fizycznej lokalizacji móc uzyskać
do niego dostęp. Zasoby mogą mieć dowolną naturę - np. mogą to być
obiekty, drukarki, komputery itp.
Serwisy nazw wiążą zasoby z identyfikatorami (nazwami).
Np. system plikowy jest swoistym serwisem nazw, który pozwala nam po nazwach odwoływać się do plików.
Przykładem innego serwisu nazw jest DNS.
Zbiór par powiązań pomiędzy nazwami a zasobami oznaczanym przez te nazwy nazywa się
kontekstem
Konteksty są zazwyczaj zorganizowane w hierarchiczne struktury, zaczynające sie od tzw. inicjalnego kontekstu (initial context). Ten kontekst - właściwy dla danego serwisu - określa konwencje nazewnicze i zawiera określone podkonteksty.
W każdym kontekście mogą być zawarte podkonteksty.
Przykład: system plikowy. Inicjalny kontekst /, podkontekst /usr itd.
W ramach kontekstu możliwe są określone operacje, np. związania nazwy z zasobem, uzyskanie dostępu do zasobu po jego nazwie.
Serwisy słownikowe poszerzają te koncepcje o możliwości nadawania
zasobom nie tylko nazw, ale i atrybutów i odpowiednie operacje
(m.in. wyszukiwania).
2. JNDI - koncepcja i architektura
W Javie dostępny jest Java Naming and Directory Interface (JNDI) -
specjalne API pozwalające uzyskiwać dostęp do serwisów nazw i
katalogów.
Operowanie na zasobach za pomocą serwisów nazw i katalogów wymaga określonych kroków:
- Uruchomienie odpowiedniego serwera nazw, dostarczającego wymaganego serwisu (zazwyczaj)
- Uzyskanie inicjalnego kontekstu dla tego serwisu (Context ctx = new InitialContext(...))
- Ew. dodawanie lub wyszukiwanie podkontekstów (przechodzenie do podkontekstów - ctx.lookup(podkontekst))
- Odnajdywanie zasobu po nazwie (ctx.lookup("NazwaZasobu")) lub wiązanie nazwy z zasobem w danym kontekście (ctx.bind("NazwaZasobu", refDoZasobu) lub ctx.rebind("NazwaZasobu", refDoZasobu)).
Architektura JNDI jest pomyślana na zasadzie "plugins". Odpowiednie
łączniki do określonych serwisów są instalowane dynamicznie w
postaci tzw. service provider implementation (SPI). Implementują one
inicjalne konteksty odpowiednie dla danego rodzaju serwisu.
źródło: JNDI Tutorial.
W standardowej dystrybucji Javy znajdują się następujace gotowe implementacje SPI:
- LDAP (LightWeight Directory Access Protocol),
- RMI - (rejestr RMI)
- CORBA - Common Object Services (COS)
name service,
- DNS.
Inne mogą być uzyskane z Internetu (m.in. ze strony JNDI, np.
filesystem service provider, umożliwiający stosowanie JNDI do systemu
plikowego, albo Windows Registry Service Provider - do dzialania na
rejestrze Windows (wersje komercyjne). Odpowiednie serwery (np. orb czy
serwer jms) mogą dostarczać swoich inicjallnych kontekstów.
Serwery aplikacji - łączą w sobie różne serwisy i dają własne
inicjalne konteksty dla różnych przypadków.
3. JNDI - praktyczne przykłady
Aby uzyskać inicjalny kontekst stosujemy konstruktor InitialContext().
Uzyskanie inicjalnego kontekstu wymaga dostarczenia parametrów m.in.
- nazwy klasy - fabryki wytwarzającej inicjalny kontekst (inicjalny kontekst będzie obiektem tej klasy),
- URLa dostawcy serwisu (service provider) dla tego inicjalnego kontekstu,
Możemy też (a czasem musimy) podać parametry związane z bezpieczeństwem (np. protokół) czy preferowanym językiem serwisu.
Wszystkie te parametry określane są jako właściwości (properties). Przy czym mogą to być:
- właściwości aplikacji, dostarczone w pliku jndi.properties,
- właściwości środowiskowe, dostarczone jako tablica asocjacyjna (Hashtable),
- właściwości systemowe, określone jako argumenty wywołania JVM (opcja -D).
Właściwości mają swoje nazwy np.:
fabryka inicjalnego kontekstu - java.naming.factory.initial
URL dostawcy serwisu - java.naming.provider.url
Zobaczmy to na przykładach.
Niech dostawcą serwisu będzie filesystem service provider (JNDI w
użyciu do systemu plikowego - trzeba ściągnąć pakiet ze strony JNDI).
Zastosowanie właściwości środowiskowych
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context initCtx = new InitialContext(env);
// Metoda lookup pozwala na odnalezienie zasobu:
Object obj = initCtx.lookup("e:/temp");
Zastosowanie właściwości aplikacji
Plik jndi.properties
java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory
Fragment programu:
Context initCtx = new InitialContext(); // konstruktor bezparametrowy
// Metoda lookup pozwala na odnalezienie zasobu:
Object obj = initCtx.lookup("e:/temp");
Zastosowanie właściwości systemowych
Program jest taki sam jak poprzednio (używamy konstruktora bezparametrowego), ale JVM wywołujemy z odpowiednimi argumentami:
java -Djava.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory Prg
(Prg jest przykładową nazwą klasy programu).
Te trzy źródła identyfikacji SPI (właściwości
środowiskowe, systemowe i właściwości aplikacji z
jndi.properties) są używane i ze sobą łączone (w wymienionej
kolejności).
Oczywiście, w realnych aplikacjach rozproszonych serwery nazw
zajmują się kontekstami, a klienci - uzyskują dostęp do
zasobów po nazwach.
Generalna procedura jest następująca:
- uruchomić odpowiedni serwer (np. ORB lub JMX), specyfikując host i port na którym działa,
- w programie A zerejestrować zasoby w inicjalnym kontekście
właściwym dla danego serwera za pomocą metody ctx.bind("NazwaZasobu")
lub ctx.rebind("NazwaZasobu")
- w programie B (który ma mieć dostęp do rozproszonych zasobów) wykonać ctx.lookup("NazwaZasobu")
Za chwilę zobaczymy to w praktycznym działaniu.
Jednak serwery aplikacji
dostarczają niejako gotowych kontekstów, do ktorych możemy
mieć dostęp z inicjalnego kontekstu (o który specjalnie nie
musimy się martwić), a wiązanie nazw z zasobami (bind) odłożone jest do fazy wdrożeniowej (deskryptory wdrożenia). Za to wiązanie odpowiedzialne są kontenery.
Dzięki temu uzyskuje się izolację kodów od konkretnych warunków i konfiguracji wdrożeniowych.
Np. znany nam już z "Metod Programowania" sposób programowania
połączeń bazodanowych w środowisku Tomcata (dający pooling połączeń):
DataSource dataSource; // źrodło danych
public void init() throws ServletException {
try {
Context init = new InitialContext();
Context contx = (Context) init.lookup("java:comp/env");
dataSource = (DataSource) contx.lookup("jdbc/ksidb");
} catch (NamingException exc) {
throw new ServletException(
"Nie mogę uzyskać źródła java:comp/env/jdbc/ksidb", exc);
}
}
W deskryptorze kontekstu (faza wdrożeniowa, plik nazywany zwykle plik context.xml) podajemy np:
<Resource name="jdbc/ksidb" auth="Container"
type="javax.sql.DataSource"
description="Baza danych ksiazek"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/ksidb"
username="admin"
password="admin"
maxActive="20" />
Zwróćmy uwagę, że ogólnie serwery J2EE dostarczają standardowego kontekstu java:comp/env,
z którego możemy mieć dostęp do różnych
podkontekstów. Np. serwer Sun Application Serwer dostarcza
następujących podkontekstów:
Rodzaj zasobu
|
Typ zasobu
|
Podkontekst JNDI
|
JDBC
|
javax.sql.DataSource
|
java:comp/env/jdbc
|
JMS
|
javax.jms.TopicConnectionFactory
javax.jms.QueueConnectionFactory
|
java:comp/env/jms
|
JavaMail
|
javax.mail.Session
|
java:comp/env/mail
|
URL
|
java.net.URL
|
java:comp/env/url
|
Connector
|
javax.resource.cci.ConnectionFactory
|
java:comp/env/eis
|
JAXR Resource Adapter
|
javax.xml.registry.ConnectionFactory
|
java:comp/env/eis/JAXR
|
Źródło: J2EE Tutorial
W środowisku serwerów aplikacji poprzez JNDI mamy też dostęp do
prostych właściwości, obiektów EJB, konektorów itp.
Np. do prostych właściwości (wartości typów opakowujących
typy pierwotne oraz typu String), specyfikowanych w deskryptorach
wdrożenia jako env-entry jest bardzo prosty:
Fragment deskryptora wdrożenia:
<env-entry>
<description>Maksymalna temperatura</description>
<env-entry-name>maxTemp</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>37</env-entry-value>
</env-entry>
i kod programu:
InitialContext iniCtx = new InitialContext();
Context envCtx = (Context) iniCtx.lookup("java:comp/env");
Integer maxExemptions = (Integer) envCtx.lookup("maxTemp");
To samo dotyczy dostępu do Enterprise Java Beans (o czym dalej).
Podsumowując: w środowisku
rozproszonych aplikacji biznesowych JNDI jest podstawowym sposobem
identyfikacji zasobów (baz danych, obiektów EJB itd.)
oraz dostępu do nich. Dzięki temu kody aplikacji mogą być odizolowane od konkretnych warunków i konfiguracji wdrożeniowych.
4. Zdalne wywołanie metod - RMI-IIOP
Remote Method Invocation over Internet Inter-ORB Protocol (RMI-IIOP)- jest wersją RMI:
- zgodną z CORBą,
- stosującą protokół transportowy IIOP-TCP/IP
- używaną m.in. przy rozproszonej komunikacji z EJB
RMI |
transport: |
JRMP (Java Remote Method Protocol) |
IIOP - TCP/IP |
|
Znany z wykładów "Metody programowania"
(tylko Java - i to jest ograniczenie) |
Umożliwia komunikację obiektów spełniająych standard
CORBA (a więc nie tylko napisanych w Javie) ze zdalnymi obiektami Javy
W stosunku do JRMP:
-
brak dynamicznych class-loaderów,
-
nie ma rozproszonego automatycznego odśmiecania,
-
do identyfikacji obiektów nie stosujemy rejestru RMI, ale JNDI realizowane przez serwis COSnaming.
|
Występują dwa podstawowe problemy przy interakcji pomiędzy obiektami w środowiskach rozproszonych:
- marshalling/unmarshalling (szeregowanie/rozszeregowanie)
- operacje konieczne, by argument wywołania zdalnej metody był w
środowisku zdalnej metody właściwie zinterpretowany (np. jak
uzyskać dostęp do obiektu, gdy do innej JVM przekazywana jest
referencja do niego - jako argument zdalnego wywołania - a sam obiekt
na tej zdalnej JVM nie istnieje)
- uwzględnienie niestabilności sieciowych - aplikacja rozproszona musi być przygotowana na niestabilności sieciowe.
Stąd szereg niezbędnych reguł przy programowaniu w RMI-IIOP:
- Wywołania metod na rzecz obiektów zdalnych odbywają
się wyłącznie w kategoriach interfejsów. Czyli klasa naszego
zdalnego obiektu musi koniecznie implementować interfejs, w
którym wyszczególnione zostały zdalne metody.
- Ten interfejs musi rozszerzać interfejs java.rmi.Remote.
- Metody tego interfejsu winny być deklarowane jako zgłaszające wyjątek java.rmi.RemoteException
(ponieważ przy ich zdalnym wywołaniu może nastąpić jakiś błąd
komunikacji sieciowej, skutkujący powstaniem właśnie takiego wyjątku).
- Klasa zdalnego obiektu oprócz implementacji zdalnego
interfejsu winna "eksportować" zdalny obiekt - czyli uczynić go zdolnym
do przyjmowania "zleceń" - zdalnych wywołań metod. Można to zrobić
poprzez odziedziczenie klasy javax.rmi.PortableRemoteObject
(zapewniając aby przy tworzeniu jej obiektów był
wywoływany konstruktor nadklasy) albo eksportować zdalny obiekt
za pomocą statycznej metody exportObject() z klasy
PortableRemoteObject. Eksportowanie obiektu oznacza jego interakcję z
usługami sieciowego, dlatego zarówno wywołanie (jawne lub
niejawne) konstruktora nadklasy jak i exportObject może - np. w
przypadku awarii sieci - powodować powstanie wyjątku RemoteException.
Dlatego sygnatura konstruktora klasy zdalnej musi uwzględniać ten
wyjątek.
Przykład:
import java.rmi.*;
import javax.rmi.*;
// Interfejs
public interface AddressInfoInterface extends Remote {
public String getAddress(String name) throws RemoteException;
}
// Implementacja
public class AddressInfo extends PortableRemoteObject
implements AddressInfoInterface {
public AddressInfo(....) throws RemoteException {
super(); // będzie wołany automatycznie, ale specjalnie zaznaczam!
//.....
}
public String getAddress(String name) throws RemoteException {
// ...
}
}
Uwaga: PortableRemoteObject pochodzi z pakietu javax.rmi
W ten sposób mamy klasę realizującą "zdalne obiekty". Do
obiektów tej klasy można sie odwoływać w środowiskach
rozproszonych. Ale trzeba powiedzieć o jaki obiekt chodzi!!!
I tu na pomoc przychodzi JNDI i serwisy nazw - w szczególności COS naming.
Po utworzeniu obiektu powinniśmy zarejestrować go pod jakąś nazwę w
JNDI. Zwykle będzie tego dokonywał odrębny program - coś jakby serwer,
udostępniający zdalne obiekty na zewnątrz (tak naprawdę będzie on nie
tyle serwerem, co pośrednikiem w komunikacji z jakimś prawdziwym
serwerem nazw). Np.
import javax.naming.*;
public class AddressInfoServer {
public static void main(String[] args) {
try {
// Utworzenie zdalnego obiektu
AddressInfo ref =
new AddressInfo(...);
// Rejestracja obiektu w serwisie nazw pod nazwą
// AddressInfoService
// Uwaga: konkretny inicjalny kontekst określą właściwości systemowe
Context ctx = new InitialContext();
ctx.rebind("AddressInfoService", ref );
} catch (Exception exc) {
e.printStackTrace();
}
}
}
Z kolei zdalne wywołanie metod po stronie klienta wymaga:
- odszukania obiektu za pomocą JNDI (od serwera nazw dostaniemy referencję typu Object),
- dokonania zawężającej konwersji do typu interfejsu zdalnego obiektu - za pomocą specjalnej metody klasy PortableRemoteObject
po czym możemy - za pośrednictwem interfejsu - wołać metody na rzecz zdalnego obiektu.
Np.
import javax.rmi.*;
import javax.naming.*;
public class AddressInfoClient {
public static void main( String args[] ) {
try {
Context ctx = new InitialContext();
Object objref = ctx.lookup("AddressInfoService");
AddressInfoInterface aif; // uwaga: zawsze interfejs!
aif = (AddressInfoInterface) PortableRemoteObject.narrow(
objref, AddressInfoInterface.class);
// zdalne wywolanie metod
String name = "Kowalski Jan";
String adres = aif.getAddress(name);
System.out.println(name + " - adres: " + adres);
} catch( Exception e ) {
e.printStackTrace( );
}
}
}
Tak to wygląda z punktu widzenia programisty.
Ale jak to jest możliwe, że metody są wołane - przecież zdalny
obiekt nie istnieje po stronie klienta, a wiec na rzecz jakiego
obiektu wołamy metody?
5. RMI-IIOP od środka
Rolę odbiorcy naszych komunikatów bierze na siebie tzw. namiastka (stub). Programista
- twórca zdalnego obiektu musi taką namiastkę utworzyć i
zapewnić, że będzie ona dostępna po stronie klienta. Na
szczęście jest to łatwe - namiastka jest tworzona automatycznie
przez kompilator rmic (z opcją -iiop). Kompilator ten tworzy - jako namiastkę - klasę o nazwie _nazwaInterfejsu_Stub (w naszym przykładzie _AddressInfoInterface_Stub).
Łatwo się o tym przekonać, dodając od kodu klienta wydruk nazwy klasy obiektu otrzymanego z JNDI:
Object objref = ctx.lookup("AddressInfoService");
System.out.println(objref.getClass().getName());
Uzyskamy:
_AddressInfoInterface_Stub
Czyli po stronie klienta odwołania idą do namiastki. A co robi namiastka?
Możemy podejrzeć. Oto zdekompilowany kod:
import java.rmi.RemoteException;
import java.rmi.UnexpectedException;
import javax.rmi.CORBA.Stub;
import javax.rmi.CORBA.Util;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.portable.ApplicationException;
import org.omg.CORBA.portable.InputStream;
import org.omg.CORBA.portable.ObjectImpl;
import org.omg.CORBA.portable.RemarshalException;
import org.omg.CORBA.portable.ServantObject;
import org.omg.CORBA_2_3.portable.OutputStream;
public class _AddressInfoInterface_Stub extends Stub
implements AddressInfoInterface
{
public _AddressInfoInterface_Stub()
{
}
public String[] _ids()
{
return _type_ids;
}
static Class _mthclass$(String s)
{
try
{
return Class.forName(s);
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
public String getAddress(String s)
throws RemoteException
{
if(!Util.isLocal(this))
try
{
org.omg.CORBA_2_3.portable.InputStream inputstream = null;
try
{
String s2;
try
{
OutputStream outputstream = (OutputStream)_request("getAddress", true);
outputstream.write_value(s, java.lang.String.class);
inputstream = (org.omg.CORBA_2_3.portable.InputStream)_invoke(outputstream);
String s1 = (String)inputstream.read_value(java.lang.String.class);
return s1;
}
catch(ApplicationException applicationexception)
{
inputstream = (org.omg.CORBA_2_3.portable.InputStream)applicationexception.getInputStream();
String s4 = inputstream.read_string();
throw new UnexpectedException(s4);
}
catch(RemarshalException _ex)
{
s2 = getAddress(s);
}
return s2;
}
finally
{
_releaseReply(inputstream);
}
}
catch(SystemException systemexception)
{
throw Util.mapSystemException(systemexception);
}
ServantObject servantobject = _servant_preinvoke("getAddress", AddressInfoInterface.class);
if(servantobject == null)
return getAddress(s);
try
{
Throwable throwable1;
try
{
String s3 = ((AddressInfoInterface)servantobject.servant).getAddress(s);
return s3;
}
catch(Throwable throwable)
{
throwable1 = (Throwable)Util.copyObject(throwable, _orb());
}
throw Util.wrapException(throwable1);
}
finally
{
_servant_postinvoke(servantobject);
}
}
private static final String _type_ids[] = {
"RMI:AddressInfoInterface:0000000000000000"
};
}
Nie wchodząc w szczegóły widać wyraźnie, że komunikacja sieciowa
odbywa się za pomocą strumieni (na niskim poziomie są to strumienie
związane z gniazdami), po których przepływają dane uformowane wg
określonych reguł (protokołu). Nie ma w tym nic tajemniczego - sami
byśmy zrobili podobnie.
Po stronie serwera udostępniającego obiekt zdalny te - symboliczne w
istocie - zapisy oraz serializowane obiekty- argumenty muszą być przez
coś odczytane, zinterpretowane i "przerobione" na faktyczne odwołania
do znajdującego się tam zdalnego obiektu. Tę rolę pełni łącznik (tie), również generowany przez rmic.
Zobaczmy co robi łącznik.
import java.rmi.Remote;
import javax.rmi.CORBA.Tie;
import org.omg.CORBA.BAD_OPERATION;
import org.omg.CORBA.ORB;
import org.omg.CORBA.Object;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.portable.ResponseHandler;
import org.omg.CORBA.portable.UnknownException;
import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.ObjectImpl;
import org.omg.CORBA_2_3.portable.OutputStream;
public class _AddressInfo_Tie extends ObjectImpl
implements Tie
{
public _AddressInfo_Tie()
{
target = null;
}
public String[] _ids()
{
return _type_ids;
}
public org.omg.CORBA.portable.OutputStream _invoke(String s, org.omg.CORBA.portable.InputStream inputstream, ResponseHandler responsehandler)
throws SystemException
{
try
{
InputStream inputstream1 = (InputStream)inputstream;
if(s.equals("getAddress"))
{
String s1 = (String)inputstream1.read_value(java.lang.String.class);
String s2 = target.getAddress(s1);
OutputStream outputstream = (OutputStream)responsehandler.createReply();
outputstream.write_value(s2, java.lang.String.class);
return outputstream;
} else
{
throw new BAD_OPERATION();
}
}
catch(SystemException systemexception)
{
throw systemexception;
}
catch(Throwable throwable)
{
throw new UnknownException(throwable);
}
}
public void deactivate()
{
_orb().disconnect(this);
_set_delegate(null);
target = null;
}
public Remote getTarget()
{
return target;
}
public ORB orb()
{
return _orb();
}
public void orb(ORB orb1)
{
orb1.connect(this);
}
public void setTarget(Remote remote)
{
target = (AddressInfo)remote;
}
public org.omg.CORBA.Object thisObject()
{
return this;
}
private AddressInfo target;
private static final String _type_ids[] = {
"RMI:AddressInfoInterface:0000000000000000"
};
}
Aha, otrzymuje zlecenie - niemal jak zwykły serwer, który
moglibyśmy napisać na gniazdach. Aha - deserializuje obiekty-argumenty.
I najważniejsze: wywołuje metody na rzecz prawdziwego obiektu:
String s2 = target.getAddress(s1);
oraz zapisuje z powrotem wyniki.
Zatem komunikacja wygląda tak:
To wszakże nie znaczy, że po stronie serwera nie jest potrzebna
namiastka. Zwróćmy uwagę od serwisu nazw klient - po nazwie -
uzyskuje referencję właśnie do namiastki. Zatem namiastka musi
być przez serwer zarejestrowana pod określoną nazwą - przez
context.bind(..) czy też rebind(). Zatem nie tylko łącznik (tie), ale i
klasa namiastki musi istnieć po stronie serwera
Pamiętajmy też że po stronie klienta potrzebny jest interfejs.
6. Uruchomienie przykładowej aplikacji
Kolejne kroki w budowie i uruchomieniu przykładowej aplikacji:
- Stworzyć interfejs (rozszerzający Remote) i określić metody zdalne (muszą deklarować sygnalizację wyjątku RemoteException)
- Implementować interfejs w klasie zdalnego obiektu
- Zapewnić eksport zdalnego obiektu (czy to przez dziedziczenie czy przez użycie metody exportObject w tej czy innej klasie)
- Skompilować interfejs i implementację (AddressInfoInterface,java i AddressInfo.java)
- Użyć rmic z opcją -iiop i argumentem - klasą implementacji do
wyprodukowania klas namiastki (powstanie
_AddressInfoInterface_Stub.class) i łącznika (powstanie:
_AddressInfo_Tie.class)
rmic -iiop AddressInfo
- Napisać i skompilować "serwer" tworzący i rejestrujący w JNDI
zdalny obiekt (zob. poprzedni wydruk AddressInfoServer.java). To tutaj
zwykle następuje eksport (przez wywołanie konstruktora lub metody
exportObject) no i nadanie nazwy obiektowi.
- Napisać i skompilować oprogramowanie wywołujące zdalnie metody obiektu (AddressInfoClient).
- Po stronie serwera umieścić:
- AddressInfo.class
- AddressInfoInterface.class
- _AddressInfoInterface_Stub.class
- _AddressInfo_Tie.class
- AddressInfoServer.class
- Po stronie klienta umieścić:
- AddressInfoInterface.class
- _AddressInfoInterface_Stub.class
- AddresInfoClient.class
- Uruchomić serwer ORB (orbd.exe), który m.in. dostarcza
serwisu nazw COS naming (jako argument należy podać wybrany port -
najlepiej jakiś wysoki numer; można też wyspecyfikowac host, domyślnie
mamy localhost)
start orbd -ORBInitialPort 3333
- Uruchomić serwer, definiując przy tym konfigurację JNDI (kto jest
dostarczycielem inicjalnego kontekstu i jak jest dostępny serwer nazw).
Możemy tę konfigurację podać w pliku jndi.properties lub jako
właściwości systemowe przy uruchamianiu wirtualnej maszyny Javy: java
-D... (uwaga: provider to serwer ORB, zatem i host i port
powinien być taki sam jak przy starcie serwera ORB):
java.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
java.naming.provider.url=iiop://localhost:3333
- Uruchomić klienta, podając taką samą konfigurację JNDI.
Zobacz prezentację omówionych kroków.
Warto wiedzieć, że w RMI-IIOP argumenty mogą być przekazywane przez wartość i przez referencję.
W przypadku przekazywania przez wartość (to jest domyślny tryb) obiekty
są serializowane i kopiowane przez sieć - zatem ew. zmiany
stanów obiektu-argumentu dokonują się na kopii i nie
dotyczą oryginału.
Przekazywanie argumentów przez referencję wymaga aby
obiekty-argumenty były same obiektami zdalnymi (czyli implementowały
Remote itd.). Dzięki temu możemy odnosić się nie do kopii, a do
oryginału - bo zdalne wywołanie metod na jego rzecz staje się możliwe.
W ten sposób zdalny obiekt na rzecz którego wywołano
metodę z argumentem-zdalnym obiektem może na rzecz argumentu zdalnie
wywoływać metody. Oczywiście, potrzebne są odpowiednie namiastki i
łączniki, a środowisko staje się bardziej symetryczne: po trochu każdy
jest tu i serwerem i klientem.
7. Zadania
Zad. 1
Wyszukać w Internecie różne rodzaje serwisów JNDI. Przetstować rozwiązania freeware'owe.
Zad. 2
Przywołać (z wykładów MPR) aplikację PhoneDirectoryServer napisaną w konwencji klient-serwer na gniazdach.
Przerobić ją na RMI-IIOP.
Porównać nakłady pracy przy jednym i przy drugim podejściu.