14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 1
14. Komunikacja rozproszona
– gniazdka, RMI
14.1 Pakiet
java.net
14.2 Mechanizm gniazdek TCP
14.3 Przykład aplikacji klient-serwer
14.4 RMI – model DOA
14.5 Przykład aplikacji rozproszonej
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 2
14.1 Pakiet java.net - wprowadzenie
1) Elementy programowania sieciowego
Aplikacje programowe w Javie korzystaj
ą
z protokółów warstwy
transportowej sieci:
„Transmission Control Protocol” (TCP) lub
„User Datagram Protocol” (UDP).
TCP
to poł
ą
czeniowy, niezawodny protokół typu point-to-point – dane
nadchodz
ą
w kolejno
ś
ci wysłania.
Z TCP korzystaj
ą
takie aplikacje, jak Hypertext Transfer Protocol
(HTTP), File Transfer Protocol (FTP), Telnet.
UDP
to bezpoł
ą
czeniowy protokół niezale
ż
nego przesyłania pakietów
danych (tzw. datagramy).
Korzystaj
ą
z niego programy nie wymagaj
ą
ce gwarantowanych
poł
ą
cze
ń
, np. serwer zegara, program ping.
Porty
- logiczne punkty wej
ś
cia do aplikacji pracuj
ą
cych na danym
komputerze, doł
ą
czonym do sieci.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 3
Dane przekazywane przez Internet posiadaj
ą
adres swojego
przeznaczenia:
adres komputera -
32-bitowy adres IP
,
adres portu -
16-bitowy numer portu
.
W komunikacji według protokółu TCP, serwer zwi
ą
zuje swoje
gniazdko
z okre
ś
lonym portem – rejestruje si
ę
pod okre
ś
lonym portem. Równie
ż
w protokóle UDP
pakiety kierowane s
ą
do zadanego portu,
przydzielonego pewnej aplikacji.
Numery portów:
od 0 do 65535
.
Numery
zastrze
ż
one
portów:
od 0 do 1023
– zarezerwowane na usługi
takie, jak HTTP, FTP, itd.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 4
Klasy w
java.net
Komunikacj
ę
zgodn
ą
z tymi protokółami TCP i UDP realizuj
ą
klasy
pakietu
java.net
:
URL, URLConnection, Socket, ServerSocket
(dla TCP);
DatagramPacket, DatagramSocket, MulticastSocket
(dla UDP).
2) Klasa
URL
URL (Uniform Resource Locator) to adres zasobu w Internecie. URL
ma posta
ć
napisu specyfikuj
ą
cego:
•
protokół
dla dost
ę
pu do zasobu i
•
lokalizacj
ę
zasobu.
URL
jest te
ż
nazw
ą
klasy w
java.net
. Obiekt klasy
URL
reprezentuje
adres URL.
Konstruktory klasy
URL
1.
Z parametrem
typu
String
reprezentuj
ą
cym
adres bezwzgl
ę
dny.
Np.:
URL adresPW = new URL("http://www.pw.edu.pl/");
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 5
2.
Z dwoma parametrami
– obiektem URL reprezentuj
ą
cym adres
bazowy
i parametrem typu
String
reprezentuj
ą
cym
adres wzgl
ę
dny
.
Np.: dane s
ą
dwa adresy
URL
http://www.ia.pw.edu.pl/dydaktyka/proz.html
http://www.ia.pw.edu.pl/dydaktyka/probe.html
Mo
ż
na utworzy
ć
obiekty klasy URL dla obu stron na podstawie
adresów wzgl
ę
dem jednej bazy -
http://www.ia.pw.edu.pl/dydaktyka/
:
URL dydaktyka = new URL("http://www.ia.pw.edu.pl/dydaktyka/");
URL dydaktykaProz = new URL(dydaktyka, "proz.html");
URL dydaktykaProbe = new URL(dydaktyka, "probe.html");
korzystaj
ą
c z konstruktora:
URL(URL bazowyURL, String wzglednyURL)
Mo
ż
na te
ż
utworzy
ć
referencj
ę
do pozycji w dokumencie
. Np.
URL dydaktykaProzProjekt = new URL(dydaktykaProz, "#PROJEKT");
3. Trzy-argumentowy konstruktor URL:
new URL("http", "www.ia.pw.edu.pl", "/dydaktyka/proz.html");
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 6
4. Cztero-argumentowy konstruktor URL uwzgl
ę
dniaj
ą
cy numer portu.
Np.
URL dydaktykaProz = new URL("http", "www.ia.pw.edu.pl”,80,
"dydaktyka/proz.html");
utworzy obiekt klasy
URL
dla nast
ę
puj
ą
cego adresu URL:
http://www.ia.pw.edu.pl:80/dydaktyka/proz.html
Ka
ż
dy
z
kontruktorów
klasy
URL
mo
ż
e
zgłosi
ć
wyj
ą
tek
MalformedURLException
(gdy argumenty referuj
ą
null
lub nieznany
protokół).
try {
URL myURL = new URL(. . .)
} catch (MalformedURLException e) {
// obsługa wyjątku
. . .
}
Obiekty klasy URL
nie mog
ą
by
ć
zmieniane
po pierwszej inicjalizacji.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 7
Podstawowe metody klasy URL
•
Metody zwracaj
ą
ce pełny adres URL dla danego obiektu klasy
URL
:
String toString()
String toExternalForm().
•
Metody podaj
ą
ce informacje o stanie obiektu URL:
getProtocol
– podaje cz
ęść
URL dotycz
ą
c
ą
protokółu;
getHost
- podaje cz
ęść
URL dotycz
ą
c
ą
adresu hosta;
getPort
- podaje cz
ęść
URL dotycz
ą
c
ą
numeru portu (liczba integer lub
warto
ść
-1 gdy port nie jest ustawiony);
getFile
- podaje cz
ęść
URL dotycz
ą
c
ą
nazwy pliku;
getRef
- podaje cz
ęść
URL dotycz
ą
c
ą
referencji w pliku dokumentu.
Przykład 14.1
. Tworzenie i korzystanie z obiektu klasy
URL
.
import java.net.*;
import java.io.*;
public class ParseURL {
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 8
public static void main(String[] args) throws Exception {
URL aURL = new URL
("http://java.sun.com:80/docs/books/"
+ "tutorial/index.html#DOWNLOADING"
);
System.out.println("protocol = " + aURL.getProtocol());
System.out.println("host = " + aURL.getHost());
System.out.println("filename = " + aURL.getFile());
System.out.println("port = " + aURL.getPort());
System.out.println("ref = " + aURL.getRef());
}
}
Wynik pracy programu:
protocol = http
host = java.sun.com
filename = /docs/books/tutorial/index.html
port = 80
ref = DOWNLOADING
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 9
Metody klasy URL do poł
ą
cze
ń
w sieci
Metoda
openStream()
Metoda zwraca obiekt strumieniowy klasy
java.io.InputStream
zwi
ą
zany z adresem
URL
.
Przykład 14.2.
Wykorzystanie metody
openStream
()
do poł
ą
czenia w
sieci. Odczyt informacji - poprzez obiekt klasy
BufferedReader
i zapis na
standardowym wyj
ś
ciu.
import java.net.*;
import java.io.*;
public class URLReader {
public static void main(String[] args) throws Exception {
URL yahoo = new URL("http://www.yahoo.com/");
BufferedReader in = new BufferedReader(
new InputStreamReader(
yahoo.openStream()));
String inputLine;
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 10
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Metoda
openConnection()
Metoda zwraca obiekt klasy
URLConnection
, który mo
ż
e by
ć
wykorzystany do szczegółowej współpracy z adresem URL. Np.
try {
URL yahoo = new URL("http://www.yahoo.com/");
URLConnection yahooConnection = yahoo.openConnection();
} catch (MalformedURLException e) {
//
new URL()
nie powiodło si
ę
. . .
}
catch (IOException e) {
//
openConnection()
nie powiodło si
ę
. . .
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 11
3) Metody klasy
URLConnection
Przykład 14.3.
Odczyt z URL
za pomoc
ą
obiektu klasy
URLConnection
.
import java.net.*;
import java.io.*;
public class URLConnectionReader {
public static void main(String[] args) throws Exception {
URL yahoo = new URL("http://www.yahoo.com/");
URLConnection yc = yahoo.openConnection();
// Poł
ą
cz
BufferedReader in = new BufferedReader(
new InputStreamReader(
yc.getInputStream()));
// Pobierz strumie
ń
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 12
Adresy URL do których doł
ą
czono
skrypty
cgi-bin
wymagaj
ą
, aby
zapisywa
ć
informacj
ę
do URL
.
Typowy przykład
zapisu do
URL
polega na wypełnieniu przez
u
ż
ytkownika zapytania podanego w postaci formatki na stronie HTML i
wysłaniu go do serwera pod adres URL. Przetwarza on zwykle skrypt
cgi-bin
po stronie serwera i odpowiada w postaci strony HTML.
Współpraca programu Javy ze skryptami cgi-bin po stronie serwera
wymaga mo
ż
liwo
ś
ci zapisu do
URL
przez ten program. Czyli wymaga
to realizacji nast
ę
puj
ą
cych kroków w rogramie:
1.
Utworzy
ć
obiekt typu
URL
.
2.
Otworzy
ć
poł
ą
czenie z tym
URL
.
3.
Ustawi
ć
mo
ż
liwo
ś
ci wyj
ś
ciowe dla
URLConnection
.
4.
Pobra
ć
strumie
ń
wyj
ś
ciowy z poł
ą
czenia – bedzie on
poł
ą
czony ze standardowym strumieniem wej
ś
ciowym
skryptu
cgi-bin
po stronie serwera.
5.
Zapisywa
ć
dane do strumienia wyj
ś
ciowego.
6.
Zamkn
ąć
strumie
ń
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 13
Przykład 14.4.
Skrypt na naszej stronie sieci odczytuje napis ze
standardowego wej
ś
cia
, odwraca kolejno
ść
znaków napisu i zapisuje
wynik d
o standardowego wyj
ś
cia
. Skrypt wymaga danych wej
ś
ciowych
w formacie
string=napis,
gdzie napis jest napisem do odwrócenia.
import java.io.*;
import java.net.*;
public class Reverse {
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Wywołanie: java Reverse " + "napis");
System.exit(1);
}
String stringToReverse = URLEncoder.encode(args[0]);
URL url = new URL("http://java.sun.com/cgi-bin/backwards");
URLConnection connection = url.openConnection();
connection.setDoOutput(true);
// Umożliwić wyjście do URL
PrintWriter out = new PrintWriter(
connection.getOutputStream());
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 14
out.println("string=" + stringToReverse);
out.close();
BufferedReader in = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Komentarz do programu:
1) Przetwarzanie parametrów programu z linii komend:
if (args.length != 1) {
System.err.println("Wywołanie: java Reverse " + "napis");
System.exit(-1);
}
String stringToReverse = URLEncoder.encode(args[0]);
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 15
Ma by
ć
tylko jeden parametr programu i kodowany on jest dla
przesłania (w tym spacje i znaki przestankowe) go do skryptu cgi-bin.
2) Utworzenie obiektu klasy URL dla skryptu na
java.sun.com
, otwarcie
URLConnection
i ustawienie poł
ą
czenia do zapisu:
URL url = new URL("http://java.sun.com/cgi-bin/backwards");
URLConnection c = url.openConnection();
c.setDoOutput(true);
// Ustawienie do zapisu
3) Utworzenie strumienia wyj
ś
ciowego
dla poł
ą
czenia i utworzenie na
nim obiektu
PrintWriter
:
PrintWriter out = new PrintWriter(c.getOutputStream());
Je
ś
li
URL
nie zezwala nam na zapis (brak wyj
ś
cia) to metoda
getOutputStream
zgłosi wyj
ą
tek
UnknownServiceException
.
4) Zapis danych do strumienia wyj
ś
ciowego i zamkni
ę
cie strumienia
:
out.println("string=" + stringToReverse);
out.close();
Strumien wyj
ś
ciowy klienta jest strumieniem wej
ś
ciowym serwera –
skryptu realizuj
ą
cego odrócenie kolejno
ś
ci liter.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 16
5) Odczytujemy informacj
ę
zwrotn
ą
ze skryptu
:
BufferReader in = new BufferedReader(
new InputStreamReader(c.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
Przykład wyniku działania:
Reverse Me
reversed is:
eM esreveR
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 17
14.2 Mechanizm gniazdek TCP
Do
komunikacji
sieciowej
z
wykorzystaniem
protokółu
TCP
wykorzystywane s
ą
mechanizmy:
strumieni i
gniazdek (ang.
socket
) - udost
ę
pniany w pakiecie
java.net.*
.
Programista mo
ż
e skupi
ć
si
ę
na tym
jakie dane chce przesła
ć
i pod
jaki adres
albo
co chce odebra
ć
i sk
ą
d.
1) Gniazdka sieciowe dla
TCP: Socket i ServerSocket
Do
bezpo
ś
redniego komunikowania
u
ż
ywany jest obiekt klasy
Socket
,
a do
prowadzenia nasłuchu
-
ServerSocket
.
Tworz
ą
c gniazdko do komunikacji
typu
Socket
podajemy adres
komputera, z którym chcemy si
ę
poł
ą
czy
ć
oraz numer portu, na którym
ma zosta
ć
nawi
ą
zane poł
ą
czenie.
Np.:
Socket s = new Socket("localhost", 4444);
- gdy chcemy poł
ą
czy
ć
si
ę
z komputerem lokalnym na porcie 4444;
Socket s = new Socket("SOLARIS", 4444);
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 18
- gdy chcemy poł
ą
czy
ć
si
ę
z komputerem maj
ą
cym nazw
ę
SOLARIS
w
sieci lokalnej LAN na porcie 4444;
Socket s = new Socket("194.29.130.225", 4444);
- gdy chcemy poł
ą
czy
ć
si
ę
z komputerem o danym adresie IP na
porcie 4444;
Socket s = new Socket("www.ia.pw.edu.pl", 4444);
- gdy chcemy poł
ą
czy
ć
si
ę
z danym serwerem internetowym na porcie
4444.
Tworz
ą
c gniazdko do nasłuchu
typu
ServerSocket
, podajemy tylko
numer portu, na którym ma by
ć
prowadzony nasłuch, np.:
ServerSocket ss = new ServerSocket(4444);
Numer portu
musi zawiera
ć
si
ę
w przedziale
[0,...,65535]
. Jednak
ż
e
niektóre numery portów s
ą
zajmowane przez usługi sieciowe takie, jak
standardowe protokoły transmisji, np.: 21 przez FTP, 80 przez HTTP.
Ponadto pewnych portów u
ż
ywaj
ą
same systemy operacyjne.
W rezultacie u
ż
ywanie numerów portów z przedziału
[0,...,1023]
jest
zabronione.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 19
2) Realizacja prostej komunikacji
Przykład
14.5.a
.
Nawi
ą
zanie
komunikacji
pomi
ę
dzy
dwoma
programami. Najpierw przedstawiamy program serwera nasłuchuj
ą
cy
na gniazdku i odbieraj
ą
cy pojawiaj
ą
ce si
ę
dane.
//Serwer.java
import java.io.*;
import java.net.*;
public class Serwer {
public static void main(String args[]) throws IOException {
ServerSocket ss = new ServerSocket(4444);
// Gniazdko nasłuchu
while(true) {
// Praca w p
ę
tli
Socket s = ss.accept();
// Oczekiwanie na poł
ą
czenie
DataInputStream dis = new DataInputStream(
s.getInputStream());
// Pobierz strumień
String msg = dis.readUTF();
// Odczyt danych ze strumienia
System.out.println(msg);
// Przetwarzaj dane – tu : wyprowadź
dis.close();
// Zamkni
ę
cie strumienia
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 20
s.close();
// Zamkni
ę
cie poł
ą
czenia – gniazdka
}
}
}
Powy
ż
szy program działa, jak praktycznie ka
ż
dy serwer,
w
niesko
ń
czonej p
ę
tli
.
Nasłuchuje
on na
porcie 4444
, korzystaj
ą
c z gniazdka
ServerSocket
.
Sam proces oczekiwania na poł
ą
czenie realizowany jest w metodzie
accept
.
Gdy na wybranym porcie pojawi si
ę
informacja o próbie uzyskania
poł
ą
czenia, nast
ę
puje
zako
ń
czenie nasłuchu
i
utworzenia
gniazdka
Socket
. Dalej nast
ę
puje
odczytanie strumienia podł
ą
czonego
do
gniazdka, zawarto
ść
jest wypisywana na ekran, a
strumie
ń
i gniazdo
s
ą
zamykane.
Nast
ę
pnie program serwera znowu czeka
na prób
ę
nawi
ą
zania
poł
ą
czenia. Proste u
ż
ycie p
ę
tli niesko
ń
czonej powoduje,
ż
e aby go
zako
ń
czy
ć
, musimy u
ż
y
ć
kombinacji klawiszy
Ctrl+C
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 21
Przykład 14.5.b.
Wysłanie danych przez klienta. Program klienta
tworzy gniazdko
Socket
w celu nawi
ą
zania poł
ą
czenia z lokalnym
komputerem na porcie 4444. Po zaakceptowaniu poł
ą
czenia tworzony
jest strumie
ń
, przez który przesyłany jest obiekt typu
String
. Nast
ę
pnie
strumie
ń
i gniazdko s
ą
zamykane, a program ko
ń
czy działanie.
//Klient.java
import java.io.*;
import java.net.*;
public class Klient {
public static void main(String args[]) throws IOException {
Socket s = new Socket("localhost", 4444);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeUTF("Witaj w sieci!");
// Prze
ś
lij napis do serwera
dos.close();
// Zamknij strumie
ń
s.close();
// Zamknij gniazdko
}
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 22
Podsumowanie przykładu 14.5
Musimy najpierw skompilowa
ć
oba programy i uruchomi
ć
program
serwerowy. Nast
ę
pnie w drugim okienku-konsoli uruchamiamy
program kliencki. Ka
ż
de kolejne uruchomienie programu-klienta
spowoduje wy
ś
wietlenie jednego napisu w programie-serwerze, co
oznacza,
ż
e poł
ą
czenie jest skutecznie nawi
ą
zywane.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 23
14.3 Przykład aplikacji klient - serwer
Aplikacje sieciowe s
ą
tworzone w celu zdalnego wykonywania jakich
ś
konkretnych czynno
ś
ci b
ą
d
ź
udost
ę
pniania usług, czy zasobów.
Zobaczmy przykład aplikacji
klient-serwer
, która - poza
nawi
ą
zaniem
ł
ą
czno
ś
ci
- b
ę
dzie realizowała
zdaln
ą
obsług
ę
trzech polece
ń
systemowych:
•
list
- wy
ś
wietlenie zawarto
ś
ci bie
żą
cego katalogu na serwerze;
•
get
- pobranie pliku z serwera i wy
ś
wietlenie go na konsoli klienta;
•
exit
- zako
ń
czenie pracy klienta.
1) Program serwera
Przykład 14.6.
Program serwera pracuje w p
ę
tli niesko
ń
czonej
nasłuchuj
ą
c na
porcie 4444
i oczekuj
ą
c na
żą
dania poł
ą
cze
ń
od
klientów. Gdy takie nadchodz
ą
- ka
ż
demu przydzielany jest osobny
w
ą
tek do obsługi.
//PlikSerwer.java
import java.io.*;
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 24
import java.net.*;
/* W
ą
tek obsługuj
ą
cy poł
ą
czenie pracuje w p
ę
tli niesko
ń
czonej. Czyta
on ze strumienia podł
ą
czonego do gniazdka obiekt serializowalnej
klasy programisty
Command
, a dokładniej - najpierw czyta obiekt ze
strumienia, a nast
ę
pnie zmienia jego typ na
Command
. */
public class PlikSerwer implements Runnable {
Socket socket;
public PlikSerwer(Socket s) {
socket = s;
}
public void run() {
// Metoda dla w
ą
tku
try {
ObjectInputStream ois =
new ObjectInputStream(socket.getInputStream());
// Strumień wejściowy
ObjectOutputStream oos =
// i wyjściowy
new ObjectOutputStream(socket.getOutputStream());
while(true) {
// P
ę
tla niesko
ń
czona
Command cmd = (Command)ois.readObject();
// Odczyt obiektu
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 25
/* Nast
ę
pnie sprawdza zawarto
ść
odczytanego obiektu i w zale
ż
no
ś
ci
od niej tworzy odpowiedni obiekt klasy programisty
Response
.
Tworzonemu obiektowi przekazywany jest efekt obsługi polecenia, tzn.
napis na zako
ń
czenie pracy, lista plików w katalogu bie
żą
cym serwera,
czy zawarto
ść
konkretnego pliku. */
// Obsługa komendy zako
ń
czenia pracy klienta
if (cmd.getCommand() == Command.EXIT) {
send(oos, new Response(cmd, "Do zobaczenia"));
break;
}
// Obsługa wy
ś
wietlania zawarto
ś
ci bie
żą
cego katalogu serwera
else if (cmd.getCommand() == Command.LIST) {
File file = new File(".");
// Plik - aktualny katalog
send(oos, new Response(cmd, file.list()));
// Lista plików
}
else if (cmd.getCommand() == Command.GET) {
// Obsługa wy
ś
wietlenia zawarto
ś
ci danego pliku na serwerze
String filename = (String) cmd.getCommandArg();
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 26
File file = new File(filename);
byte buff [] = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(buff);
// Wczytaj dane z podanego pliku
fis.close();
send(oos, new Response(cmd, buff));
// Prze
ś
lij zawarto
ść
pliku
}
else { send(oos, new Response(cmd, "Nieznana komenda")); }
}
ois.close();
// Zamknij strumienie wej
ś
ciowy
oos.close();
// -“- wyj
ś
ciowy
socket.close();
// Zamknij gniazdko
}
catch(Exception e) {
System.out.println(e);
}
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 27
/* Obiekt zapisywany jest do strumienia wyj
ś
ciowego – czyli resultat
przesyłany jest do klienta w obiekcie klasy
Response
. */
private void send(ObjectOutputStream oos, Response response)
throws IOException {
oos.writeObject(response);
oos.flush();
}
/* Program serwera pracuje w p
ę
tli niesko
ń
czonej nasłuchuj
ą
c na
porcie 4444
i oczekuj
ą
c na
żą
dania poł
ą
cze
ń
od klientów. Gdy takie
nadchodz
ą
- ka
ż
demu przydzielany jest osobny w
ą
tek do obsługi. */
public static void main(String args[]) throws IOException {
ServerSocket ss = new ServerSocket(4444);
// Utwórz gniazdko
System.out.println("Nasłuchiwanie na porcie 4444...");
while(true) {
Socket s = ss.accept();
// Oczekuje na
żą
danie od klienta
System.out.println("Akceptuję połączenie z: "+s.getInetAddress());
(new Thread(new PlikSerwer(s))).start();
// Uruchom w
ą
tek obsługi
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 28
}
}
2) Klasy pomocnicze w tym przykładzie
Przykład 14.7
. Opiszemy tu klas
ę
pomocnicz
ą
serwera
Command.
Klasa pomocnicza zawiera deklaracje stałych identyfikuj
ą
cych
polecenia wydawane serwerowi przez klienta. Zawiera te
ż
zmienn
ą
okre
ś
laj
ą
c
ą
bie
żą
ce polecenie wydane serwerowi oraz metody j
ą
obsługuj
ą
ce. Cała klasa jest serializowana, aby jej obiekty mogły by
ć
zapisywane do strumieni.
//Command.java
import java.io.*;
public class Command implements Serializable {
public final static int LIST = 0;
public final static int GET = 1;
public final static int EXIT = 2;
private int cmd;
private Object arg;
public Command (int cmd) {
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 29
this.cmd = cmd;
}
public Command (int cmd, Object arg) {
this.cmd = cmd;
this.arg = arg;
}
public int getCommand() {
return cmd;
}
public Object getCommandArg() {
return arg;
}
}
Przykład 14.8.
Opiszemy teraz klas
ę
Response
. Klasa zawiera prywatne
pole do przechowywania obiektu b
ę
d
ą
cego rezultatem realizacji
polecenia klienta. Dzi
ę
ki serializacji obiekty tej klasy mog
ą
by
ć
przesyłane za pomoc
ą
strumieni, tak samo jak obiekty
Command
.
// Response.java
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 30
import java.io.*;
public class Response implements Serializable {
private Object data;
private Command cmd;
public Response (Command cmd, Object data) {
this.cmd = cmd;
this.data = data;
}
public Object getData() {
return data;
}
public Command getCommand() {
return cmd;
}
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 31
3) Program klienta w tym przykładzie
Przykład 14.9.
Program klienta.
// PlikKlient.java
import java.io.*;
import java.net.*;
public class PlikKlient {
Socket socket;
ObjectOutputStream oos;
ObjectInputStream ois;
public PlikKlient(String host, int port) throws IOException {
socket = new Socket(host, port);
oos = new ObjectOutputStream(socket.getOutputStream());
ois = new ObjectInputStream(socket.getInputStream());
}
public void go() {
try {
BufferedReader br = new BufferedReader(
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 32
new InputStreamReader(System.in));
while(true) {
System.out.print("Zdalny>");
System.out.flush();
String line = br.readLine();
// Komenda końca pracy klienta
if (line.startsWith("exit")) {
Command cmd = new Command(Command.EXIT);
Response response = send(cmd);
System.out.println(response.getData());
System.exit(0);
}
//Komenda wyświetlenia zawartości bieżącego katalogu serwera
else if (line.startsWith("list")) {
Command cmd = new Command(Command.LIST);
Response response = send(cmd);
String list [] = (String []) response.getData();
for (int i=0; i<list.length; i++) {
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 33
System.out.println(list[i]);
}
}
//Komenda wyświetlenia zawartości danego pliku na serwerze
else if (line.startsWith("get")) {
Command cmd = new Command(Command.GET, line.substring(4));
Response response = send(cmd);
byte content [] = (byte []) response.getData();
System.out.println(new String(content));
}
else {
System.out.println("Nieznana komenda: "+line);
}
}
}
catch (Exception e) {
System.out.println(e);
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 34
}
private Response send(Command cmd) throws Exception {
oos.writeObject(cmd);
oos.flush();
return (Response) ois.readObject();
}
public static void main(String args[]) throws IOException {
(new PlikKlient("localhost", 4444)).go();
}
}
Program klienta odczytuje z konsoli polecenia od u
ż
ytkownika,
zapisuje je w postaci obiektów do strumienia podł
ą
czonego do
gniazdka pracuj
ą
cego na porcie 4444, odczytuje ze strumienia
przychodz
ą
ce z serwera odpowiedzi i wy
ś
wietla je na konsoli.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 35
4) Uruchomienie całej aplikacji
Po skompilowaniu wszystkich klas uruchamiamy serwer, a nast
ę
pnie
klienta (lub wielu klientów) - oczywi
ś
cie ka
ż
dy z programów
uruchamiany jest w osobnym okienku konsoli.
Gdy oba programy znajduj
ą
si
ę
na jednym komputerze (tj. testujemy
poł
ą
czenie na komputerze lokalnym), nie musz
ą
by
ć
umieszczone w
jednym katalogu. Jednak
ż
e wtedy zarówno w katalogu z serwerem -
jak i z klientem - musz
ą
znajdowa
ć
si
ę
skompilowane klasy
pomocnicze.
Po uruchomieniu obu programów mo
ż
emy wyda
ć
w konsoli klienta
które
ś
z trzech obsługiwanych polece
ń
i zobaczy
ć
efekt jego działania.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 36
14.4 RMI (Remote Method Invocation)
– model DOA
1) Model DOA
Mechanizm RMI wykorzystuje
model komunikacji klient-serwer
i
realizuje paradygmat programowania obiektowego - rozproszonego
(
DOA – distributed object application
):
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 37
program serwera
tworzy obiekty, udost
ę
pnia referencje do nich
(dzi
ę
ki ich zarejestrowaniu poprzez
rmiregistry
) i oczekuje na
wywołania metod na tych obiektach inicjowane przez klientów;
programy klientów
uzyskuj
ą
referencj
ę
do zdalnego obiektu i
wywołuj
ą
na nim metody.
2) Typowy schemat współpracy klient - serwer w RMI
(A) Zdalny interfejs i zdalny obiekt
Aplikacja z u
ż
yciem
Java RMI
składa si
ę
z interfejsów i klas, przy czym
spodziewamy si
ę
,
ż
e implementacje metod mog
ą
by
ć
rozproszone po
ró
ż
nych maszynach.
Zdalnym obiektem
nazywamy obiekt, którego metody mog
ą
by
ć
wywoływane z ró
ż
nych maszyn wirtualnych Javy. Takie metody
deklarowane s
ą
w tzw.
zdalnych interfejsach
:
zdalny interfejs dziedziczy po interfejsie
java.rmi.Remote
,
w li
ś
cie wyj
ą
tków ka
ż
dej metody tego interfejsu istnieje deklaracja
wyj
ą
tku
java.rmi.RemoteException
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 38
Serwer
zwi
ą
zuje nazw
ę
w rejestrze ze swoim
zdalnym obiektem,
który mo
ż
e by
ć
wołany zdalnie.
(B) Pobranie obiektu i wywołanie metody
Klient
odnajduje zdalny obiekt w
rejestrze
poprzez jego nazw
ę
i
wywołuje
na nim
metod
ę
interfejsu.
RMI nie kopiuje zdalnego obiektu do programu “odbiorcy” lecz
przekazuje “
namiastk
ę
” (stub)
zdalnego obiektu – reprezentuje ona
referencj
ę
do zdalnego obiektu. Ta referencja mo
ż
e by
ć
konwertowana
na dowolny
zdalny interfejs
implementowany przez klas
ę
zdalnego
obiektu. Klasa zdalnego obiektu pełni w programie klienta rol
ę
po
ś
rednika (proxy) w przekazie danych.
Zdalnie mo
ż
na wywoła
ć
wył
ą
cznie metody zdalnych interfejsów danej
klasy
.
(C) Dynamiczne ładowanie definicji klasy
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 39
Je
ś
li klasa obiektu
nie jest zdefiniowana
na maszynie “odbiorcy
obiektu” to RMI pobierze od „nadawcy” i prze
ś
le ten kod.
Mechanizm RMI korzysta z istniej
ą
cego
serwera sieciowego
dla
przekazywania
bajtkodu klas pomi
ę
dzy klientem a serwerem
– w obie
strony.
3) Podstawowe klasy i interfejsy w
java.rmi
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 40
4) Tworzenie aplikacji rozproszonej w
RMI
Nale
ż
y:
zdefiniowa
ć
komponenty aplikacji (lokalne i zdalne obiekty, zdalne
interfejsy, implementacje metod);
skompilowa
ć
program (kompilator
javac
) i wygenerowa
ć
„namiastki”
zdalnych obiektów (kompilator
rmic
);
udost
ę
pni
ć
klasy i namiastki obiektów w sieci,
uruchomi
ć
aplikacj
ę
(dokona
ć
rejestracji w rejestrze zdalnych
obiektów RMI, uruchomi
ć
serwer i klientów).
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 41
14.5
Przykład
aplikacji
rozproszonej
o
dynamicznie
ładowanym kodzie
Przykład 14.10.
Projekt
serwera oblicze
ń
w oparciu o mechanizm RMI
Serwer akceptuje zadania pochodz
ą
ce od klienta, wykonuje te zadania
i zwraca wyniki. Serwer składa sie ze zdalnego interfejsu
Compute
i
klasy
ComputeEngine
implementuj
ą
cej ten interfejs, przeznaczonej dla
utworzenia zdalnego obiektu. W metodzie
main
tej klasy nast
ę
puje
utworzenie tego obiektu, jego zarejestrowanie i ustawienie menad
ż
era
zabezpiecze
ń
(security manager).
Interfejsy typu “Remote Interface”
W przykładzie wyst
ę
puj
ą
2 interfejsy zdalne deklaruj
ą
ce po jednej
metodzie.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 42
Interfejs
Compute
serwera
ComputeEngine
pozwala na dostarczanie
zadania do wykonania.
package compute;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Compute extends Remote {
Object executeTask(Task t) throws RemoteException;
}
Dziedziczenie z interfejsu
java.rmi.Remote
oznacza,
ż
e:
metody
dziedziczonego interfejsu mog
ą
by
ć
wołane z dowolnej
maszyny wirtualnej Javy;
obiekt z implementacj
ą
tego interfejsu staje si
ę
zdalnym obiektem
.
Zdalna
metoda
musi
deklarowa
ć
zgłaszanie
wyj
ą
tku
java.rmi.RemoteException
. Jest to
sprawdzalny wyj
ą
tek
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 43
Interfejs klienta
Task
wyznacza sposób wykonania przez serwer
powierzonego zadania.
package compute;
import java.io.Serializable;
public interface Task extends Serializable {
Object execute();
}
Dziedziczenie interfejsu po
java.io.Serializable
oznacza,
ż
e obiekt klasy
implementuj
ą
cej ten interfejs mo
ż
e zosta
ć
zamieniony na strumie
ń
bajtów i jednoznacznie zrekonstruowany z niego.
Obiekty ró
ż
nych klas mog
ą
zosta
ć
przekazane do obiektu
Compute
,
je
ś
li tylko klasy implementuj
ą
interfejs
Task.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 44
Implementacja zdalnego interfejsu
Klasa u
ż
ytkownika
engine.ComputeEngine
implementuje interfejs zdalny
Compute
, a w jej metodzie
main
inicjalizowany jest obiekt tej klasy.
package engine;
import java.rmi.*;
import java.rmi.server.*;
import compute.*;
public class ComputeEngine extends UnicastRemoteObject
implements Compute {
// Konstruktor –
public ComputeEngine() throws RemoteException {
super();
// Wywoła metod
ę
exportObject
// Konstruktor nadklasy został by wywołany – nawet przy braku
super();
}
// Metoda dla pobrania obiektu klienta do wykonania
public Object executeTask(Task t) {
return t.execute(); //
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 45
}
// Metoda
main
dla ustawienia obiektu
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
String name = "//host/Compute";
// Okre
ś
la nazw
ę
obiektu
// zdalnego serwera
try {
Compute engine = new ComputeEngine();
// Utworzy obiekt
Naming.rebind(name, engine);
// Rejestracja obiektu zdalnego
System.out.println("Compute: związany");
} catch (Exception e) {
System.err.println("Compute: wyjątek " + e.getMessage());
e.printStackTrace();
}
}
// Koniec metody
main
!
}
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 46
Komentarz
1) Klasa serwera implementuje zdalny interfejs:
public class ComputeEngine extends UnicastRemoteObject
implements Compute
i dziedziczy po klasie bazowej
java.rmi.server.UnicastRemoteObject
.
Ta klasa zapewnia podstawow
ą
funkcjonalno
ść
zdalnego obiektu:
implementuje metody klasy
java.lang.Object
(
equals, hashCode
,
toString
) odpowiednio dla zdalnego obiektu;
zawiera konstruktory i metody statyczne przewidziane do
wyeksportowania zdalnego obiektu (
exportObject()
) tzn. aby zdalny
obiekt mógł odbiera
ć
wezwania od klientów;
zapewnia poł
ą
czenia „point-to-point” w oparciu o
gniazdka
.
Inna klasa dla zdalnych obiektów
java.rmi.activation.Activatable
(dla
zdalnych obiektów wywoływalnych na
żą
danie).
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 47
2) Konstruktor klasy
ComputeEngine
:
public ComputeEngine() throws RemoteException {
super();
// Nast
ę
puje tu te
ż
eksportowanie obiektu
}
Nale
ż
y zadeklarowa
ć
zgłaszalno
ść
w
ą
tku
RemoteException
, który
zostanie zgłoszony, gdy nie powiedzie si
ę
eksport obiektu.
3) Implementacje metod interfejsów zdalnych
Klasa implementuje jedn
ą
metod
ę
interfejsu zdalnego
Compute
metod
ę
executeTask
:
public Object executeTask(Task t) {
return t.execute();
}
Metoda wyznacza sposób komunikacji pomi
ę
dzy obiektem klasy
ComputeEngine
a jego klientami. Klienci przekazuj
ą
serwerowi obiekt
klasy implementuj
ą
cej interfejs
Task
i posiadaj
ą
cej implementacj
ę
metody
execute
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 48
4) Klasa klienta
Do serwera mo
ż
e by
ć
przekazany obiekt dowolnego typu o ile jest to:
zmienna typu prostego, obiekt zdalny lub serializowalny obiekt (tzn.
jego klasa implementuje interfejs
java.io.Serializable
).
Sposoby przekazywania obiektów zdalnych, parametrów i wyników.
Zdalne obiekty
przekazywane s
ą
przez
referencj
ę
. Po stronie klienta
tworzona jest
namiastka obiektu
(stub) pełni
ą
ca rol
ę
proxy
(przekazuj
ą
c
ą
odwołania do rzeczywistego obiektu serwera).
Zwykłe obiekty
przekazywane s
ą
przez
warto
ść
poprzez ich
serializacj
ę
. W zdalnym wywołaniu metody – zwykłe obiekty
(parametry, zwracany wynik, wyj
ą
tki) s
ą
przekazywane przez
warto
ść
–
tworzona jest ich kopia w zdalnej wirtualnej maszynie Javy.
5) Metoda
main
Metoda
main()
klasy
ComputeEngine
nie jest elementem interfejsu, czyli
nie jest metod
ą
wywoływaln
ą
zdalnie.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 49
W pierwszej kolejno
ś
ci metoda
main()
powinna utworzy
ć
i zainstalowa
ć
zarz
ą
dc
ę
polityki bezpiecze
ń
stwa (
Security Manager
), w przeciwnym
razie mechanizm
RMI
nie zezwoli na ładowanie zdalnych klas.
W
RMI
dost
ę
pna jest klasa
RMISecurityManager
, która realizuje
pdobn
ą
polityk
ę
bezpiecze
ń
stwa, jak ta stosowana dla apletów.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
Nast
ę
pnie
utworzony
zostaje
obiekt
klasy
ComputeEngine
i
udost
ę
pniony klientom na anonimowym porcie:
Compute engine = new ComputeEngine();
// Utworzenie obiektu
Mechanizm RMI zarz
ą
dza
rejestrem zdalnych obiektów
, z którego
mo
ż
na pobra
ć
referencj
ę
do zdalnego obiektu na podstawie jego
nazwy. Rejestr ten mo
ż
e by
ć
wspólny dla wszystkich serwerów na
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 50
danej maszynie albo te
ż
proces serwera mo
ż
e posiada
ć
odr
ę
bny taki
rejestr.
Metody dost
ę
pu do rejestru zdalnych obiektów wyznacza interfejs
java.rmi.Naming
(zwi
ą
zanie, rejestracja, pobranie).
W metodzie
main
klasy
ComputeEngine
nadajemy nazw
ę
String name = "//host/Compute";
//
host
to nazwa maszyny
//
Compute
to nazwa identyfikuj
ą
ca obiekt zdalny w rejestrze.
i rejestrujemy obiekt
engine
klasy
ComputeEngine
w rejestrze:
Naming.rebind(name, engine);
Podczas wywołania
rebind()
mo
ż
e powsta
ć
wyj
ą
tek:
RemoteException
.
Parametry wywołania metody
Naming.rebind
:
pierwszy parametr jest typu
java.lang.String
o postaci adresu
URL
;
Np.
//host:1234/objectname
Je
ś
li brak jest numeru portu to przyjmuje si
ę
domy
ś
lny port: 1099.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 51
Po zarejestrowaniu obiektu zdalnego i wydruku informacji o gotowo
ś
ci
do pracy metoda
main()
ko
ń
czy si
ę
wykonywa
ć
(
w
ą
tek główny ko
ń
czy
swoje wykonanie !).
Jednak
istnieje referencja
do obiektu przekazana innemu obiektowi –
bed
ą
cemu rejestrem zdalnych obiektów.
Mechanizm RMI pilnuje, aby proces dla obiektu
ComputeEngine
nadal
istniał.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 52
Przykład 14.11
Program klienta musi definiowa
ć
zadanie, które ma
wykona
ć
w jego imieniu serwer. W tym przykładzie klient składa si
ę
z 2
klas:
ComputePi
– w celu pobrania obiektu serwera
Compute
;
Pi
-
klasa implementuj
ą
ca interfejs
Task
i definiuj
ą
ca zadanie.
Definicja
Task
:
package compute;
public interface Task extends java.io.Serializable {
Object execute();
}
Klasa klienta
client.ComputePi
package client;
import java.rmi.*;
import java.math.*;
import compute.*;
public class ComputePi {
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 53
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
// Nazwa serwera w formacie URL
String name = "//" + args[0] + "/Compute";
// Pobierz referencje do obiektu zdalnego serwera:
Compute comp = (Compute) Naming.lookup(name);
Pi task = new Pi(Integer.parseInt(args[1]));
// Wywołaj metod
ę
executeTask
obiektu zdalnego
comp
// przekazuj
ą
c zadanie do wykonania
task
:
BigDecimal pi = (BigDecimal) (comp.executeTask(task));
System.out.println(pi);
// Wydrukuj obliczony wynik
} catch (Exception e) {
System.err.println("ComputePi exception: " +
e.getMessage());
e.printStackTrace();
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 54
}
}
}
Przepływ informacji pomi
ę
dzy obiektem klienta klasy
ComputePi
,
rejestrem obiektów zdalnych
rmiregistry
i obiektem serwera
ComputeEngine
.
Komentarz
1) Klasa klienta instaluje menad
ż
era bezpiecze
ń
stwa
Jest to niezb
ę
dne, gdy
ż
RMI mo
ż
e ładowa
ć
kod do procesu klienta – w
tym przypadku namiastk
ę
obiektu
ComputeEngine
.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 55
Podobnie jak serwer równie
ż
klient korzysta tu z menad
ż
era
bezpiecze
ń
stwa dost
ę
pnego w systemie RMI.
2) Klient tworzy nazw
ę
do nadzoru obiektu zdalnego typu
Compute
Pierwszy parametr z linii wywołania programu,
args[0]
, stanowi nazw
ę
zdalnej maszyny, na której wykonuje si
ę
object typu
Compute
.
Klient odwołuje si
ę
do metody
Naming.lookup
w celu odszukania
obiektu po jego nazwie w rejestrze zdalnych obiektów zdalnego hosta.
Parametr metody (typu
String
) ma t
ę
sam
ą
składni
ę
adresu URL jak
parametr metody
Naming.rebind
w przypadku rejestracji obiektu przez
serwer.
3) Klient tworzy nowy obiekt klasy
Pi
Argumentem wywołania konstruktora
Pi
jest drugi paramer z inii
wywołania programu klienta,
args[1]
, podaj
ą
cy liczb
ę
pozycji
dziesi
ę
tnych dla oblicze
ń
.
4) Klient wywołuje metod
ę
executeTask
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 56
Klient wywołuje metod
ę
executeTask
zdalnego obiektu typu
Compute
.
Defnicja klasy
client.Pi
, która implementuje inerfejs
Task
:
package client;
import compute.*;
import java.math.*;
public class Pi implements Task {
/** Stałe potrzebne dla obliczenia watości pi */
private static final BigDecimal ZERO = BigDecimal.valueOf(0);
private static final BigDecimal ONE = BigDecimal.valueOf(1);
private static final BigDecimal FOUR = BigDecimal.valueOf(4);
/** Tryb zaokrąglania */
private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;
/** Precyzja - liczba pozycji po kropce dziesiętnej */
private int digits;
/** Konstruktor */
public Pi(int digits) {
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 57
this.digits = digits;
}
/** Oblicz pi */
public Object execute() {
return computePi(digits);
}
/** według formuły
* pi/4 = 4*arctan(1/5) - arctan(1/239)
* i po rozwinięciu arctan(x) w szereg.
*/
public static BigDecimal computePi(int digits) {
int scale = digits + 5;
BigDecimal arctan1_5 = arctan(5, scale);
BigDecimal arctan1_239 = arctan(239, scale);
BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
arctan1_239).multiply(FOUR);
return pi.setScale(digits,
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 58
BigDecimal.ROUND_HALF_UP);
}
/**
* Oblicza wartość arct tangens w radianach według wzoru:
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + (x^9)/9 ...
*/
public static BigDecimal arctan(int inverseX, int scale)
{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 =
BigDecimal.valueOf(inverseX * inverseX);
numer = ONE.divide(invX, scale, roundingMode);
result = numer;
int i = 1;
do {
numer =
numer.divide(invX2, scale, roundingMode);
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 59
int denom = 2 * i + 1;
term =
numer.divide(BigDecimal.valueOf(denom),
scale, roundingMode);
if ((i % 2) != 0) { result = result.subtract(term);
} else { result = result.add(term);
}
i++;
} while (term.compareTo(ZERO) != 0);
return result;
}
}
Uwaga
Obiekt klasy
Compute
nie wymaga definicji klasy
Pi
dopóki obiekt klasy
Pi
nie zostanie przekazany jako argument metody
executeTask
. W tym
momencie kod klasy ładowany jest przez RMI do maszyny wirtualnej
wła
ś
ciwej dla obiektu typu
Compute
, wołana jest metoda
execute
klasy
Pi
i wykonuje si
ę
kod tej metody. Wynikowy obiekt typu
Object
, w tym
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 60
przypadku jest on typu
java.math.BigDecimal
, jest przekazywany
zwrotnie do klienta, w którym nast
ę
puje wydruk tego obiektu.
Z punktu widzenia serwera – obiektu typu
ComputeEngine
– jest bez
znaczenia, co oblicza zlecona metoda. Jedyne co musi wiedzie
ć
obiekt
klasy implementuj
ą
cej
Compute
o przekazywanym obiekcie to to,
ż
e
jego klasa posiada metod
ę
execute
.
Dynamiczne ładowanie klas
Poni
ż
szy rysunek ilustruje sk
ą
d ładowane s
ą
definicje klas potrzebnych
programom:
rmiregistry
, serwerowi
ComputeEngine
, i klientowi
ComputePi
podczas wykonania rozproszonej aplikacji Javy.
Gdy serwer
ComputeEngine
rejestruje (bind) swój zdalny obiekt w
rejestrze, rejestr pobiera klas
ę
ComputeEngine_Stub
i interfejsy
Compute
i
Task
, od której ta klasa zale
ż
y. Te klasy pobierane s
ą
z
serwera sieciowego wła
ś
ciwego dla
ComputeEngine
lub z systemu
plików.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 61
Klient typu
ComputePi
ładuje klas
ę
ComputeEngine_Stub
z serwera
sieci wła
ś
ciwego dla
ComputeEngine
w wyniku wykonania metody
Naming.lookup
. Klient
ComputePi
dysponuje opisami interfejsów
Compute
i
Task
, dlatego te
ż
ładowane s
ą
one z jego lokalnego
katalogu klas.
Na koniec, do maszyny wirtualnej wykonuj
ą
cej serwer
ComputeEngine
ładowany jest kod klasy
Pi
, gdy obiekt klasy
Pi
przekazywany jest w
zdalnym wywołaniu metody
executeTask
na rzecz obiektu typu
ComputeEngine
– klasa
Pi
ładowana jest z serwera sieciowego
wła
ś
ciwego dla klienta.
14. Komunikacja rozproszona
W. Kasprzak: Programowanie zdarzeniowe
14 - 62
Kompilacja i uruchomienie aplikacji
(serwera i klientów) oraz obliczenie
warto
ś
ci
π
.
1) Tworzymy pliki typu JAR (Java ARchive) zawieraj
ą
cy interfejsy
Compute
i
Task
dla implementacji ich przez klasy serwera i z
których korzysta program klienta.
2) Tworzymy implementacj
ę
interfejsu
Compute
i instalujemy t
ę
usług
ę
na maszynie udost
ę
pniaj
ą
c j
ą
klientom.
3) Twórcy programów klientów, korzystaj
ą
c z interfejsów
Compute
i
Task
, zawartych w pliku JAR, mog
ą
niezale
ż
nie od siebie utworzy
ć
zadanie (
Task
) i program klienta korzystaj
ą
cego z usługi
Compute
.
Klasa klienta
Pi
ładowana jest na serwer podczas wykonania.
Podobnie zdalna namiastka dla
ComputeEngine
ładowana jest z
serwera na maszynie klienta podczas wykonania.
Mamy trzy pakiety:
•
compute ( interfejsy
Compute
i
Task
)
•
engine (klasa implementuj
ą
ca
ComputeEngine
i jej namiastka)
•
client (kod klienta
ComputePi
i implementacja zadania
Pi
).