1/1 Systemy rozproszone Dr inż. L. Miękina Department of Robotics and Mechatronics AGH University of Science and Technology Marzec, 2013
Program wykładu Podstawowe charakterystyki, architektury Komunikacja sieciowa z przykładami w j. Java Zdalne wywoływanie procedur (Remote Procedure Calling) Sieciowe systemy plików (Network File System) Zdalne obiekty: Java Remote Method Invocation i CORBA Usługi katalogowe Web Services 2 / 1
Przeznaczenie Motywacja Współdzielenie zasobów jest głównym powodem budowy systemów rozproszonych. Zasoby te są zarządzane przez serwery i udostępniane klientom, lub są opakowywane jako obiekty i udostępniane innym obiektom-klientom. Definicja system rozproszony (SR) to taki, którego składniki zlokalizowane na komputerach należących do sieci komunikują się i koordynują swoje działania tylko za pomocą przesyłanych komunikatów (brak pamięci wspólnej - dzielonej). Definicja system rozproszony jest kolekcją niezależnych komputerów, które dla użytkowników sprawiają wrażenie jednolitego, zwartego systemu. Problemy wynikające z konstrukcji SR: niejednorodność (heterogeneity) składników otwartość, która pozwala dodawać lub wymieniać składniki bezpieczeństwo skalowalność - zdolność do poprawnej pracy przy zwiększającej się liczbie użytkowników tolerowanie awarii równoległość funkcjonowania składników przeźroczystość (położenia, migracji,...) 3 / 1
Charakterystyki SR SR mają następujące główne charakterystyki: równoległość funkcjonowania składników: w sieci komputerów, równoległe wykonywanie programów jest normą - wielu użytkowników może równocześnie pracować, współdzieląc zasoby (strony w sieci lub pliki, etc.). W tym przypadku, koordynacja równolegle działających programów, które używają wspólnych zasobów jest istotnym zagadnieniem. brak globalnego zegara: programy w SR kooperują wymieniając komunikaty. Ścisła koordynacja zwykle zależy od wspólnego czasu występowania zdarzeń lub wykonywania operacji. Jednak ograniczone możliwości synchronizowania zegarów komputerów w sieci sprawiają, że brak jest pojęcia poprawnego czasu systemowego (jako że jedyny sposób komunikacji to wymiana komunikatów) niezależne awarie komponentów: SR mogą ulegać swoistym awariom. Awarie sieci skutkują izolacją połączonych komputerów. Podobnie, awaria komputera lub niespodziewane zakończenie programu (crash) nie jest natychmiast widoczna dla innych współpracujących komputerów. Dlatego każdy komponent może ulegać awarii niezależne, pozostawiając inne w stanie działania i oczekiwania na dalszą współpracę. 4 / 1
Przykłady SR Typowymi, dobrze znanymi przykładami SR są: Internet, który jest ogromną kolekcją wzajemnie połączonych sieci komputerowych wielu różnych typów. Programy działające na komputerach dołączonych do tych sieci wzajemnie na siebie oddziałują za pomocą uzgodnionych środków komunikacji (definiowanych przez protokoły internetowe). Intranet, będący wydzieloną częścią Internetu, która jest oddzielnie administrowana i ma konfigurowane granice aby wymusić lokalne strategie bezpieczeństwa. Intranet połączony z Internetem poprzez router, który pozwala użytkownikom pracującym wewnątrz intranetu używać usług poza nim (Web, e-mail, ftp, etc.). Aby chronić intranet przed nieuprawnionym dostępem używa się tzw. zapory (firewall). Działanie zapory polega na filtrowaniu przychodzących i wychodzących komunikatów, na przykład w zależności od ich źródła lub przeznaczenia. Np. tylko komunikaty związane z pocztą elektroniczną lub WWW są przekazywane, a inne są blokowane. sieci urządzeń mobilnych, w tym laptopów i komunikatorów; urządzeń wbudowanych w wyposażenie mieszkań i budynków (pralki, audio, tv, ogrzewanie i klimatyzacja, etc.) aplikacje przemysłowe, w tym systemy korporacyjne i wytwórcze. 5 / 1
Modele SR Wyróżnia się dwa rodzaje modeli SR: modele architektury modele fundamentalne (podstawowe) Modele architektury SR są związane z: rozmieszczeniem części w sieci komputerów, w celu identyfikacji użytecznych wzorców rozkładu danych i obciążenia relacjami między częściami, uwzględniającymi ich funkcjonalne role i szablony komunikacji wzajemnej. Przykładami są modele klient-serwer i procesy równorzędne (peer processes). Modele podstawowe są związane z bardziej formalnym opisem własności, które są wspólne dla wszystkich architektur. model interakcji ma związek z wydajnością i z zagadnieniami określenia limitów czasowych w SR, na przykład dla dostarczania komunikatów model awarii określa wszystkie rodzaje niesprawności, jakie mogą powstać w działaniu procesów i kanałów komunikacyjnych. Definiuje niezawodną komunikację i poprawne procesy. model bezpieczeństwa analizuje możliwe zagrożenia dla działania procesów i kanałów komunikacyjnych. Wprowadza koncepcję bezpiecznego kanału, który jest odporny na te (znane) zagrożenia. 6 / 1
Modele architektury Warstwy oprogramowania Warstwy oprogramowania pomagają w opisie oprogramowania z punktu widzenia usług, jakich ono dostarcza. Warstwy w samodzielnej maszynie: komputer i interfejs sieciowy (karta) system operacyjny aplikacje i usługi. Warstwy w maszynie działającej w SR: komputer i interfejs sieciowy (karta) system operacyjny warstwa pośrednia (middleware) aplikacje i usługi. W obu przypadkach, dwie pierwsze warstwy (komputer i interfejs sieciowy (karta); system operacyjny) tworzą platformę dla rozproszonych systemów i aplikacji. Platforma dostarcza usług dla następnej warstwy, która jest zaimplementowana niezależnie na każdym komputerze, dostarczając możliwości programowania rozproszonego na wyższym poziomie (funkcje do komunikacji i synchronizacji między procesami. Typowe platformy: Intel x86/windows, Intel x86/linux, Sun SPARC/SunOS. 7 / 1
Modele architektury Warstwa pośrednia (middleware) Definicja Warstwa pośrednia jest warstwą oprogramowania, której zadaniem jest ukrycie całej różnorodności rozwiązań (dot. sprzętu, sieci, systemów operacyjnych i języków programowania) i dostarczenie wygodnego i wydajnego modelu programowania dla twórców rozproszonych aplikacji. Warstwa pośrednia jest zorganizowana z procesów i obiektów powołanych na każdym z komputerów, które współdziałają w celu implementacji zadań systemu rozproszonego. W szczególności, warstwa pośrednia odpowiada za komunikację, udostępniając abstrakcje typu zdalne wywoływanie procedur (remote procedure call) i zdalne wywoływanie metod (remote method invocation). Najbardziej znane implementacje warstwy pośredniej to: Remote Procedure Call (Sun) Remote Method Invocation (Sun) Common Object Request Broker Architecture ( OMG) Distributed Component Object Model (Microsoft). 8 / 1
Modele architektury Klient-serwer Model klient-serwer jest historycznie najbardziej znaczący i ciągle pozostaje najszerzej stosowany. W tym modelu, procesy klientów współpracują z poszczególnymi procesami serwerów działającymi na oddzielnych komputerach w celu dostępu do dzielonych zasobów. Serwery mogą z kolei być klientami innych serwerów, jak na schemacie. Na przykład, serwer WWW jest zwykle klientem lokalnego serwera plików, który zarządza plikami w których zapisano strony WWW. Poza tym, serwer WWW jest zwykle klientem usługi DNS. 9 / 1
Modele architektury Usługi dostarczane przez wiele serwerów Usługi mogą być realizowane przez pewną liczbę procesów serwera działających na oddzielnych komputerach, które to procesy kooperują ze sobą by dostarczyć usługę procesom klienta. Serwery mogą rozdzielać (partycjonować) pomiędzy siebie ogół obiektów na których jest oparta usługa, lub mogą utrzymywać repliki tych obiektów na pewnej grupie hostów. Sieć WWW jest typowym przykładem partycjonowania danych, jako że każdy serwer WWW zarządza swoim zbiorem zasobów (stron). Replikacja jest używana by zapewnić lepszą wydajność i dostępność danych i zwiększyć odporność na awarie. Replikacja zabezpiecza istnienie wielu identycznych kopii danych w procesach działających na różnych komputerach. 10 / 1
Modele architektury Servery proxy i pamięć cache Pamięć cache jest składnicą ostatnio używanych danych dotyczących obiektów, które to dane są bliższe (łatwiej dostępne) niż same obiekty. Kiedy nowy obiekt zostaje przesłany do danego komputera, jest dodawany do lokalnej pamięci cache, czasem zastępując starszy obiekt. Kiedy dany obiekt ma być udostępniony, usługa cache sprawdza zawartość pamięci cache i zwraca zapisany tam obiekt, jeśli jest on aktualny. Pamięci cache mogą być związane z poszczególnymi klientami lub mogą być umieszczone na serwerze proxy, który jest wspólny dla wielu klientów. 11 / 1
Modele architektury Procesy równorzędne (peer processes) W tej architekturze wszystkie procesy pełnią takie same role, razem kooperując jako równorzędni partnerzy (peers) aby wykonać rozproszone działanie lub obliczenia, bez różnicowania na klientów i serwery. Eliminacja procesów serwera redukuje opóźnienia wynikające z komunikacji między procesowej realizowanej dla dostępu do lokalnych obiektów. Na przykład, aplikacja pozwalająca wielu użytkownikom na wspólną pracę nad dokumentami tekstowymi (podgląd i modyfikacja zawartości). Kod powiadamiający o zmianach zawartości dokumentów może być zlokalizowany na każdym komputerze, umożliwiając natychmiastowe poinformowanie pozostałych zainteresowanych. 12 / 1
Modele architektury Wariacje modelu klient-serwer: mobilny kod Mobilny kod oznacza kod, który może być przesłany z jednego komputera na inny i tam uruchomiony. Często wymaga to uruchomienia go w specjalnym środowisku, tzw. wirtualnej maszynie na docelowym komputerze. Typowym przykładem są applety - użytkownik pracujący w przeglądarce wybiera hiperłącze identyfikujące applet, którego kod jest przechowywany na serwerze WWW. Kod ten jest następnie ściągany z serwera i uruchamiany w przeglądarce. Korzyść wykonywania kodu lokalnie: dobra interaktywność, bo brak opóźnień i ograniczeń przepustowości w komunikacji sieciowej. Wada: potencjalne zagrożenie bezpieczeństwa dla zasobów lokalnych docelowego komputera, dlatego applety zwykle mają ograniczone prawa dostępu, kontrolowane przez używaną maszynę wirtualną. Mobilny agent jest działającym programem (włącznie z kodem i danymi), który przemieszcza się z jednego komputera na drugi, realizując pewne zadanie na rzecz użytkownika (np. zbieranie informacji). Mobilny agent może w różny sposób wykorzystywać lokalne zasoby, np. odczytywać rekordy baz danych, etc. Użycie mobilnych agentów pozwala zredukować obciążenie związane ze zdalnym dostępem, ale wprowadza podobne zagrożenia bezpieczeństwa jak użycie kodu mobilnego. 13 / 1
Modele architektury Komputery sieciowe Używając komputerów typu desktop należy zwykle zainstalować wszystkie potrzebne programy lokalnie i zarządzać danymi. Może to być problemem dla mniej doświadczonych użytkowników. Rozwiązaniem jest używanie komputera sieciowego (network computer), który ładuje system operacyjny i wszystkie programy aplikacji ze zdalnego serwera plików. Aplikacje wykonują się lokalnie, ale pliki są zarządzane przez zdalne serwery plików. Korzyści: Użytkownicy nie są przywiązani do jednego komputera, ale mogą łatwo przenosić się na inne komputery sieciowe Wymagania sprzętowe są zredukowane. Jeśli komputer sieciowy posiada dysk, przechowuje on tylko podstawowe oprogramowanie. Pozostała część dysku jest używana jako pamięć podręczna (cache), przechowująca kopie programów i danych, jakie były ostatnio używane (czyli ładowane z serwera plików). Zarządzanie pamięcią cache jest automatyczne - obiekty są z niej usuwane, gdy nowa wersja pliku jest zapisana na serwerach plików z którymi współpracuje dany komputer sieciowy. 14 / 1
Modele architektury Cienki klient (thin client) Nazwa cienki klient (thin client) odnosi się do warstwy oprogramowania, która prezentuje okienkowy interfejs użytkownika na lokalnym komputerze, podczas gdy wszystkie programy są uruchamiane na zdalnym serwerze. Taka architektura cechuje się podobnie niskimi wymaganiami sprzętowymi i kosztami eksploatacji jak komputer sieciowy, ale w miejsce ładowania kodu aplikacji z serwera - wykonuje ten kod na tzw. serwerze obliczeniowym lub serwerze aplikacji (compute server) - b. wydajny komputer, mogący równolegle wykonywać wiele aplikacji (wieloprocesorowy lub klastrowy). Implementacje tej architektury: Systemy UNIX i Linux używają systemu okien X-11, który zarządza wyświetlaczem i interaktywnymi urządzeniami wejściowymi (klawiatura, mysz, etc) komputera na którym działa. X-11 dostarcza szerokiej gamy bibliotek funkcji, stanowiących protokół X-11, używanych do wyświetlania i manipulacji obiektów graficznych w oknach. X-11 jest nazywany serwerem okien (window server process), klientami są natomiast wszystkie aplikacje aktualnie używane w systemie. Procedury serwera są wywoływane z użyciem mechanizmu RPC. Citrix WinFrame dostarcza procesu cienkiego klienta, który może być uruchomiony na wielu platformach w celu emulacji pulpitu mającego dostęp do programów działających w systemie WinNT na zdalnych hostach. 15 / 1
Komunikacja międzyprocesowa To zadanie jest realizowane przez specjalną warstwę oprogramowania, nazywaną pośrednią (middleware), która jest zaimplementowana na szczycie warstw odpowiedzialnych za komunikację sieciową, dlatego opiera się na protokołach UDP i TCP. Warstwa pośrednia dzieli się na dwie podwarstwy: niższa obsługuje protokół zapytanie odpowiedź (request-reply protocol), szeregowanie (marshalling) i zewnętrzną reprezentację danych (external data representation), na bazie UDP i TCP. wyższa obsługuje zdalne wywołanie procedur (remote procedure calling - RPC) i zdalne wywołanie metod (remote method invocation - RMI). RPC i RMI są abstrakcjami dostępnymi z poziomu języków programowania, takich jak C, C++ i Java. Przykładami systemów zdalnego wywołania metod są CORBA i Java RMI. 16 / 1
Komunikacja międzyprocesowa Określenia podstawowe Przekazywanie komunikatów między dwoma procesami wymaga dwu operacji: send i receive. Aby proces mógł się komunikować z innym procesem, musi on wysłać komunikat, będący sekwencją bajtów do miejsca przeznaczenia, a tam inny proces odbiera ten komunikat. Miejsce przeznaczenia posiada kolejkę komunikatów. Proces wysyłający powoduje dodanie komunikatu do zdalnej kolejki, a proces odbierający pobiera komunikat z lokalnej kolejki. Wyróżnia się dwa podstawowe rodzaje komunikacji: synchroniczną, w której procesy wysyłający i odbierający synchronizują się wzajemnie przy każdym komunikacie, a operacje send i receive są blokujące. Każde wywołanie send powoduje zablokowanie procesu wysyłającego do czasu gdy odpowiadająca mu operacja receive jest wykonana. Każde wywołanie receive powoduje zablokowanie procesu odbiorcy do czasu odebrania komunikatu. asynchroniczną, w której operacja send jest nie-blokująca - proces wysyłający może kontynuować swoje działanie od momentu skopiowania komunikatu do lokalnego bufora transmisji, po czym sama transmisja zachodzi równolegle z normalnym działaniem procesu wysyłającego. Operacja receive może mieć wariant blokujący lub nie-blokujący. W drugim przypadku, proces odbiorczy kontynuuje normalne działanie po wywołaniu receive. Operacja receive ma przekazany bufor na komunikat, który ma zapełnić w tle i powiadomić proces odbierający o tym zdarzeniu poprzez przerwanie lub polling. 17 / 1
Komunikacja międzyprocesowa Punkty dostarczania komunikatów W obrębie protokołów Internet-u, komunikaty są kierowane do pary (adres IP, lokalny port). Definicja: lokalny port jest punktem dostarczania komunikatów w komputerze, identyfikowanym przez numer, będący wartością całkowitą krótką. Port może być skojarzony z co najwyżej jednym odbiorcą, ale z wieloma źródłami. Proces może używać wielu portów do odbioru komunikatów. Serwery zwykle publikują numery swoich portów, przeznaczonych dla klientów. Dowolny proces znający numer portu serwera może wysłać do niego komunikat (o uzgodnionym formacie). Jeśli klienci mają używać ustalonych adresów IP serwera do dostępu do jego usług, to usługi serwera muszą być zawsze uruchamiane na tym samym komputerze. Inne rozwiązania: klienci wywołują usługi po nazwie i używa się serwera nazw (lub tzw. binder-a) do translacji nazw na adresy w czasie działania systemu. To pozwala stosować relokację usług, ale nie pozwala stosować migracji (zmiana położenia w czasie działania systemu) używanie identyfikatorów niezależnych od położenia, o ile system operacyjny na to pozwala. To umożliwia relokację i migrację usług. 18 / 1
Komunikacja międzyprocesowa Gniazda (sockets) Definicja: gniazdo jest abstrakcją, która stanowi punkt połączeniowy komunikacji międzyprocesowej. Komunikacja międzyprocesowa polega na transmisji komunikatów pomiędzy gniazdem jednego procesu i gniazdem drugiego procesu. Aby proces mógł odbierać komunikaty, jego gniazdo musi być przypisane do lokalnego portu i jednego z adresów IP komputera, na którym proces działa. Komunikaty wysłane pod dany adres IP i port mogą być odebrane tylko przez proces, który ma gniazdo skojarzone z tym adresem i portem. Procesy mogą używać tego samego gniazda do wysyłania i odbioru komunikatów Każdy komputer ma dużą ilość (2 16 ) portów przeznaczonych do użytku przez lokalne procesy do odbioru komunikatów Procesy mogą używać wielu portów do odbioru komunikatów, ale nie mogą współdzielić portów z innymi procesami na tym samym komputerze. Wyjątkiem są procesy używające techniki IP multicast - one mogą dzielić porty Dowolna ilość procesów może wysyłać komunikaty do tego samego portu Każde gniazdo jest skojarzone z konkretnym protokołem (UDP lub TCP) 19 / 1
Komunikacja międzyprocesowa Adresy internetowe w Javie Klasa InetAddress reprezentuje adresy internetowe (IP). Użytkownicy tej klasy odwołują się do komputerów za pomocą nazw hostów usługi Domain Name Service. Na przykład, aby utworzyć obiekt reprezentujący adres internetowy hosta, który ma nazwę DNS galaxy.uci.agh.edu.pl, należy napisać: InetAddress ahost = InetAddress.getByName("galaxy.uci.agh.edu.pl"); Metoda getbyname może wygenerować wyjątek UnknownHostException gdy host o podanej nazwie nie istnieje, dlatego należy zadbać o odpowiednią obsługę tego wyjątku - czego tu nie pokazano! Użytkownicy tej klasy nie muszą znać adresu IP ani wnikać w szczegóły formatu adresu (czy jest to IPv4 albo IPv6). 20 / 1
Komunikacja międzyprocesowa Protokół UDP Datagram przesyłany z użyciem UDP jest transmitowany bez potwierdzania i powtarzania w przypadku błądu. Jeśli wystąpi błąd, komunikat może w ogóle nie być odebrany. Proces który zamierza wysyłać lub odbierać komunikaty musi najpierw utworzyć gniazdo przypisane do adresu IP i lokalnego portu. Proces serwera przypisuje swoje gniazdo do tego portu serwera, który jest udostępniony klientom. Proces klienta przypisuje swoje gniazdo do dowolnego nie zajętego portu. Metoda receive zwraca poza komunikatem, adres IP i numer portu nadawcy, pozwalając odbiorcy komunikatu wysłać odpowiedź. Nie-blokująca się metoda send: kończy ona działanie, gdy dostarczy komunikat do bufora protokołu UDP, który zajmuje się transmisją do gniazda docelowego. Po dostarczeniu, komunikat jest umieszczony w kolejce gniazda przypisanego do odbiorcy i może być odebrany przez wywołanie metody receive danego gniazda. Komunikaty są niszczone po osiągnięciu punktu docelowego (adres IP i port), gdy nie istnieje proces mający gniazdo przypisane do tego portu. Blokująca się metoda receive: receive czeka na odebranie datagramu, o ile nie określono timeout-u dla gniazda. Jeśli proces wywołuje receive i musi kontynuować swoje działanie, to powinien w tym celu powołać nowy wątek. Metoda receive blokująca się w nieskończoność jest potencjalnie niebezpieczna, dlatego możliwe jest ustanowienie timeout-u dla gniazda, aby wyeliminować możliwość zawieszenia się serwera w oczekiwaniu na utracony komunikat. 21 / 1
Komunikacja międzyprocesowa UDP w Javie Java wspiera komunikację UDP za pomocą dwu klas: DatagramPacket, która reprezentuje pakiet zawierający datagram. Instancje klasy DatagramPacket mogą być transmitowane między procesami. Klasa ma 6 konstruktorów, m.in: ctor który tworzy instancję na podstawie tablicy bajtów zawierających komunikat, jego długości i adresu IP i numeru lokalnego portu gniazda w procesie odbiorczym. ctor do użytku przy odbiorze komunikatu. Wymaga tylko 2 argumentów: tablicy bajtów gdzie znajdzie się odebrany komunikat i długości komunikatu. Instancja klasy mieści odebrany komunikat, jego długość, adres IP i port. Komunikat może być udostępniony za pomocą metody getdata. DatagramSocket, która obsługuje gniazda używane do wysyłania i odbioru datagramów UDP. Klasa ma 5 konstruktorów, m.in: ctor bez parametrów, dla procesów mogących użyć dowolnego portu ctory z argumentami typów: (int port), (int port, InetAddress addr), (SocketAddress addr). Te ctory generują wyjątek SocketException gdy port jest już zajęty, lub wybrano tzw. znany port (well-known port) (w systemie UNIX/Linux). 22 / 1
Komunikacja międzyprocesowa UDP w Javie Klasa DatagramSocket ma dodatkowe metody, m.in: send i receive służą do transmisji datagramów miedzy gniazdami. Argumentem metody send jest instancja klasy DatagramPacket, zawierająca komunikat i adres. Argumentem metody receive jest pusta instancja klasy DatagramPacket, w której po powrocie znajdzie się odebrany komunikat, wraz z danymi o długości i źródle. Obie metody mogą wygenerować wyjątek IOException. setsotimeout pozwala ustawić timeout dla gniazda. Wtedy metoda receive blokuje się na ten okres czasu, a jeśli komunikat nie nadejdzie to generowany jest wyjątek InterruptedIOException. connect jest używana do połączenia z wybranym zdalnym portem na maszynie mającej znany adres IP. W tym przypadku gniazdo może wysyłać i odbierać komunikaty tylko z tego adresu. Dla ilustracji tych zagadnień dalej dyskutowane są przykłady kodu programów klienta i serwera. Serwer najpierw tworzy gniazdo przypisane do swojego portu 9876, a następnie czeka w pętli na komunikat od klienta, po jego odbiorze odpowiada wysyłając jego kopię do klienta. Klient najpierw tworzy gniazdo, wysyła komunikat do serwera na port 9876 i czeka na odpowiedź. 23 / 1
Komunikacja międzyprocesowa UDP w Javie: przykład kodu serwera 1 public class UDPServer { 2 public static void main ( String [] args ) { 3 try { 4 // args contain message content and server hostname 5 DatagramSocket asocket = new DatagramSocket (9876); 6 byte [] buffer = new byte [1024]; 7 while ( true ) { 8 DatagramPacket request = new DatagramPacket (buffer, buffer. length ); 9 asocket. receive ( request ); 10 DatagramPacket reply = new DatagramPacket ( request. getdata (), 11 request. getlength (), request. getaddress (), request. getport ()); 12 asocket.send ( reply ); 13 } 14 } catch ( SocketException ex) { 15 Logger. getlogger ( UDPServer. class. getname ()). log ( Level.SEVERE, null, ex ); 16 } catch ( IOException ex) { 17 Logger. getlogger ( UDPServer. class. getname ()). log ( Level.SEVERE, null, ex ); 18 } 19 } 20 } 24 / 1
Komunikacja międzyprocesowa UDP w Javie: przykład kodu klienta 12 public class UDPClient { 13 public static void main ( String [] args ) { 14 try { 15 if (args. length < 2) { 16 System.out. println (" Usage : UDPClient <msg > <server host name >"); 17 System.exit ( -1); 18 } 19 DatagramSocket asocket = new DatagramSocket (); 20 byte [] msg = args [0]. getbytes (); 21 System.out. println ("Sent : " + args [0]); 22 InetAddress ahost = InetAddress. getbyname (args [1]); 23 int serverport = 9876; 24 DatagramPacket request = new DatagramPacket (msg, args [0]. length (), ahost, serverpo 25 asocket.send ( request ); 26 byte [] buffer = new byte [1024]; 27 DatagramPacket reply = new DatagramPacket (buffer, buffer. length ); 28 asocket. receive ( reply ); 29 System.out. println (" Reply : " + new String ( reply. getdata ())); 30 asocket. close (); 31 } catch ( SocketException ex) { 32 Logger. getlogger ( UDPClient. class. getname ()). log ( Level.SEVERE, null, ex ); 33 } catch ( UnknownHostException ex) { 34 Logger. getlogger ( UDPClient. class. getname ()). log ( Level.SEVERE, null, ex ); 35 } catch ( IOException ex) { 36 Logger. getlogger ( UDPClient. class. getname ()). log ( Level.SEVERE, null, ex ); 37 } 38 } 39 40 } 25 / 1
Komunikacja międzyprocesowa Komunikacja z użyciem protokołu TCP API wspierające komunikację TCP dostarcza abstrakcji będącej strumieniem bajtów, do którego dane mogą być zapisane i z którego dane mogą być odczytane. Abstrakcja ta ukrywa pewne cechy sieci, takie jak: rozmiar komunikatu: aplikacja może decydować, jak dużo danych zapisuje lub odczytuje ze strumienia. Bazowa implementacja protokołu strumieni TCP decyduje jak dużo danych zebrać przed wysłaniem pakietu IP. komunikaty utracone: protokół TCP używa potwierdzeń. Jeśli nadawca nie otrzyma potwierdzenia w czasie <= od ustawionego timeout-u, to retransmituje komunikat. kontrola przepływu (flow control): protokół TCP stara się sterować szybkością transmisji, aby wyrównać prędkości nadawania i odbioru. Jeśli proces zapisujący jest za szybki dla procesu odczytującego, to jest on blokowany aż proces odczytujący upora się z danymi. duplikaty i uporządkowanie: pakiet IP ma przypisany identyfikator, co pozwala odbiorcy znaleźć i usunąć duplikaty, lub uporządkować pakiety, które zostały dostarczone w innej kolejności. przeznaczenie komunikatów: para komunikujących się procesów ustanawia połączenie zanim zaczną wymieniać dane przez strumień. Kiedy połączenie jest ustanowione, procesy mogą po prostu zapisywać do i odczytywać ze strumienia, bez używania adresów IP i numerów portów. Ustanowienie połączenia wymaga wysłania żądania connect od klienta do serwera, a następnie wysłania żądania accept od serwera do klienta. 26 / 1
Komunikacja międzyprocesowa Komunikacja z użyciem protokołu TCP Kiedy para komunikujących się procesów ustanawia połączenie, jeden z nich pełni rolę klienta, a drugi pełni rolę serwera. Rola klienta wymaga utworzenia gniazda przypisanego do dowolnego portu, a następnie wysłania żądania connect w celu uzyskania połączenia z gniazdem zdalnego serwera. Rola serwera wymaga utworzenia gniazda nasłuchującego, które jest przypisane do portu serwera i oczekującego na żądania połączeń od klientów. Nasłuchujące gniazdo zarządza kolejką, w której umieszczane są żądania połączeń nadchodzące od klientów. Kiedy serwer akceptuje połączenie, tworzone jest nowe gniazdo do komunikacji serwera z danym klientem, zachowując oryginalne gniazdo dla nasłuchiwania żądań połączeń od innych klientów. Para gniazd (klienta i serwera) jest połączona parą strumieni, po jednym w każdym kierunku. A więc każde gniazdo ma strumień wejściowy i strumień wyjściowy. Dzięki temu jeden z procesów może wysłać informację do drugiego, zapisując w swoim strumieniu wyjściowym, a drugi proces otrzymuje tę informację przez odczyt ze swego strumienia wejściowego. UWAGA: dwa komunikujące się procesy muszą uzgodnić format danych wymienianych przez strumień: jeśli jeden proces zapisuje wartość całkowitą i zmiennoprzecinkową, to drugi musi czytać dane w tej samej kolejności i odpowiednio je interpretować. Jeśli dwa procesy nie kooperują odpowiednio, odbiorca może napotkać błędy przy interpretowaniu danych lub może zostać zablokowany w oczekiwaniu na dane. 27 / 1
Komunikacja międzyprocesowa TCP w Javie: przykład kodu klienta 1 public class TCPClient { 2 public static void main ( String args []) { 3 // arguments supply message and hostname 4 if (args. length < 2) { 5 System.out. println (" Usage : TCPClient <server host name > <msg >"); 6 System.exit ( -1); 7 } 8 Socket socket = null ; 9 try { 10 int serverport = 7896; 11 socket = new Socket (args [0], serverport ); 12 DataInputStream in = new DataInputStream ( socket. getinputstream ()); 13 DataOutputStream out = new DataOutputStream ( socket. getoutputstream ()); 14 // args = ArrayUtils. remove (args, 0); // import org. apache. commons.lang. ArrayUtils ; 15 // System. arraycopy (args, 1, nargs, 0, args.length -1); 16 args [0] = ""; 17 String message = String. join (" ", args ); 18 out. writeutf ( message ); // UTF is a string encoding 19 String data = in. readutf (); // read a line of data from the stream 20 System.out. println (" TCPClient received : " + data ); 21 } 22 catch ( UnknownHostException e) { System.out. println (" Socket :" + e. getmessage ()); } 23 catch ( EOFException e) { System.out. println ("EOF :" + e. getmessage ()); } 24 catch ( IOException e) { System.out. println (" readline :" + e. getmessage ()); } 25 finally { 26 if ( socket!= null ) 27 try { socket. close (); } 28 catch ( IOException e) { System.out. println (" close :" + e. getmessage ()); } 29 } 30 } 31 } 28 / 1
Komunikacja międzyprocesowa TCP w Javie: przykład kodu serwera 1 package tcpserver ; 2 3 import java. net.*; 4 import java. util. Date ; 5 import java.io.*; 6 7 public class TCPServer { 8 public static void main ( String args []) { 9 System.out. println (" TCPServer waiting... "); 10 try { 11 int serverport = 7896; // the server port 12 ServerSocket listensocket = new ServerSocket ( serverport ); 13 while ( true ) { 14 Socket clientsocket = listensocket. accept (); 15 new Connection ( clientsocket ); 16 } 17 } 18 catch ( IOException e) { 19 System.out. println (" Listen socket :" + e. getmessage ()); 20 } 21 } 22 } 29 / 1
Komunikacja międzyprocesowa TCP w Javie: przykład kodu serwera 24 class Connection extends Thread { 25 DataInputStream in; 26 DataOutputStream out ; 27 Socket clientsocket ; 28 29 public Connection ( Socket aclientsocket ) { 30 try { 31 clientsocket = aclientsocket ; 32 in = new DataInputStream ( clientsocket. getinputstream ()); 33 out = new DataOutputStream ( clientsocket. getoutputstream ()); 34 System.out. println (" Connection established "); 35 this. start (); 36 } 37 catch ( IOException e) { 38 System.out. println (" Connection :" + e. getmessage ()); 39 } 40 } 41 42 public void run () { 43 try { // an echo server 44 String data = in. readutf (); // read a line of data from the stream 45 System.out. println (" TCPServer received : " + data ); 46 Date date = new Date (); 47 out. writeutf ("It s " + date. tostring ()); 48 } 49 catch ( EOFException e) { System.out. println ("EOF :" + e. getmessage ()); } 50 catch ( IOException e) { System.out. println (" readline :" + e. getmessage ()); } 51 finally { 52 try { clientsocket. close (); } 53 catch ( IOException e) { System.out. println (" close :" + e. getmessage ()); } 54 } 55 } 56 } 30 / 1
Komunikacja międzyprocesowa TCP w C: przykład kodu klienta 1 # include <sys / socket.h> 2 # include < netinet /in.h> 3 # include <netdb.h> 4 # define SERVER_PORT 12345 // arbitralne, klient i server 5 # define BUF_SIZE 4096 // rozmiar bloku 6 int main (int argc, char ** argv ) { 7 int c, s, bytes ; 8 char buf [ BUF_SIZE ]; // bufor dla odebranego pliku 9 struct hostent *h; // info o serwerze 10 struct sockaddr_in channel ; // struktura adresowa : domena, port, adres IP 11 if (argc!= 3) fatal (" Usage : client server -name file -name "); 12 h = gethostbyname ( argv [1]); // odczyt adresu IP hosta 13 if (!h) fatal (" gethostbyname failed "); 14 s = socket ( PF_INET, SOCK_STREAM, IPPROTO_TCP ); // zwraca deskryptor gniazda 15 if (s <0) fatal (" socket "); 16 memset (& channel, 0, sizeof ( channel )); 17 channel. sin_family = AF_INET ; 18 memcpy (& channel. sin_addr.s_addr, h->h_addr, h->h_length ); 19 channel. sin_port= htons ( SERVER_PORT ); 20 c = connect (s, ( struct sockaddr *) &channel, sizeof ( channel )); 21 if (c < 0) fatal (" connect failed "); 22 // Polaczenie ustanowione. Przeslij nazwe pliku zakonczona bajtem 0. 23 write (s, argv [2], strlen (argv [2])+1); 24 // Pobierz plik i przeslij na stdout. 25 while (1) { 26 bytes = read (s, buf, BUF_SIZE ); // czytanie z gniazda 27 if ( bytes <= 0) exit (0); // sprawdzenie konca 28 write (1, buf, bytes ); // przeslij na stdout 29 } 30 } 31 fatal ( char * string ) { 32 printf ("%s\n", string ); 33 exit (1); 31 / 1
Komunikacja międzyprocesowa TCP w C: przykład kodu serwera 1 // Kod serwera 2 # include <sys / types.h> 3 # include <sys / fcntl.h> 4 # include <sys / socket.h> 5 # include < netinet /in.h> 6 # include <netdb.h> 7 8 # define SERVER_PORT 12345 // arbitralne, klient i server to samo 9 # define BUF_SIZE 4096 // rozmiar bloku 10 # define QUEUE_SIZE 10 11 12 int main ( int argc, char * argv []) { 13 int s, b, l, fd, sa, bytes, on = 1; 14 char buf [ BUF_SIZE ]; // bufor dla wysylanego pliku 15 struct sockaddr_in channel ; // adres IP 16 // Utworz strukture zaw. adres gniazda. 17 memset (& channel, 0, sizeof ( channel )); // zeruj strukture gniazda 18 channel. sin_family = AF_INET ; 19 channel. sin_addr. s_addr = htonl ( INADDR_ANY ); 20 channel. sin_port = htons ( SERVER_PORT ); 21 // Pasywne otwarcie. Czekaj na polaczenie. 22 s = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); // tworz gniazdo 23 if (s < 0) fatal (" socket failed "); 24 setsockopt (s, SOL_SOCKET, SO_REUSEADDR, ( char *) &on, sizeof (on )); 32 / 1
Komunikacja międzyprocesowa TCP w C: przykład kodu serwera 25 b = bind (s, ( struct sockaddr *) &channel, sizeof ( channel )); 26 if (b < 0) fatal (" bind failed "); 27 l = listen (s, QUEUE_SIZE ); // rozmiar kolejki 28 if (l < 0) fatal (" listen failed "); 29 // Gniazdo przygotowane. Czekaj na polaczenie i obsluz je 30 while (1) { 31 sa = accept (s, 0, 0); // oczekuj na zadanie 32 if (sa < 0) fatal (" accept failed "); 33 read (sa, buf, BUF_SIZE ); // odczytaj nazwe pliku z gniazda 34 // Otworz plik i zwroc uchwyt. 35 fd = open (buf, O_RDONLY ); // otworz wysylany plik 36 if (fd < 0) fatal (" open failed "); 37 while (1) { 38 bytes = read (fd, buf, BUF_SIZE ); // odczyt pliku 39 if ( bytes <= 0) break ; // sprawdzenie konca 40 write (sa, buf, bytes ); // wyslij bufor do gniazda 41 } 42 close (fd ); // zamknij plik 43 close (sa ); // zamknij polaczenie 44 } 45 } 46 47 fatal ( char * string ) { 48 printf ("%s\n", string ); 49 exit (1); 50 } 33 / 1
Komunikacja międzyprocesowa Zewnętrzna reprezentacja danych Informacja zawarta w działającym programie jest reprezentowana przez struktury danych - np. zbiór połączonych obiektów - podczas gdy informacja wewnątrz komunikatu ma postać ciągu bajtów. W konsekwencji, dane muszą być spłaszczone (przekształcone na ciąg bajtów) przed transmisją i odtworzone po odebraniu. Dodatkowo, indywidualne dane typów prostych transmitowane w komunikacie mogą używać odmiennych reprezentacji na odmiennych platformach. W celu umożliwienia wymiany danych w tych warunkach stosuje się jedną z metod: wartości danych są przekształcane do uzgodnionego zewnętrznego formatu przed transmisją i przekształcane do lokalnego formatu po odebraniu ich; jeśli dwa komputery są tego samego typu to konwersję pomija się. wartości danych są transmitowane w formacie obowiązującym na maszynie nadawcy, razem z informacją o używanym formacie, a odbiorca przekształca je w razie potrzeby. Aby zapewnić możliwość użycia RPC lub RMI, każdy typ danych przesyłanych jako argument lub zwracanych jako rezultat musi być serializowalny, a pojedyncze dane typów prostych muszą być reprezentowane w uzgodnionym formacie, nazywanym zewnętrzną reprezentacją danych (external data representation). 34 / 1
Komunikacja międzyprocesowa Szeregowanie (Marshalling) Szeregowanie jest to proces zamiany kolekcji danych o dowolnej organizacji na formę możliwą do transmisji w postaci komunikatu. Wymaga to więc translacji danych o pewnej strukturze i danych prostych do postaci zgodnej z zewnętrzną reprezentacją danych. Deszeregowanie jest to proces odtwarzania oryginalnej kolekcji danych, które odebrano w postaci, jaką definiuje zewnętrzna reprezentacja danych. Wymaga to odtworzenia danych prostych z ich zewnętrznej reprezentacji i odtworzenia złożonych struktur danych. Znane rozwiązania XDR i szeregowania: Sun RPC XDR CORBA Common Data Representation, która może być używana w wielu językach programowania serializacja obiektów w Javie, która pozwala spłaszczać i przekształcać do postaci XDR dowolny pojedynczy obiekt lub drzewo obiektów, które mają być transmitowane w komunikacie lub zapisane na dysku. We wszystkich wymienionych przypadkach, wymagane operacje są obsługiwane przez warstwę pośrednią (middleware layer) bez konieczności angażowania programisty aplikacji rozproszonej. 35 / 1
Komunikacja międzyprocesowa Protokół request-reply 36 / 1
RPC Architektura Proces serwera definiuje w swoim interfejsie usług procedury dostępne dla zdalnych wywołań. RPC jest zaimplementowane z wykorzystaniem protokołu żądanie-odpowiedź (request-reply protocol). Konfiguracja oprogramowania pracującego zgodnie z RPC: RPC zostało wprowadzone przez firmę Sun Microsystems w 1995 r. dla komunikacji w modelu klient-serwer obsługującym sieciowy system plików Sun NFS. Rozwiązanie to jest też nazywa ONC (Open Network Computing) RPC i jest dostępne jako moduł systemu operacyjnego Sun Solaris, innych systemów Unix i Linux, a także wielu innych SO. 37 / 1
RPC Działanie Proces klienta, który zamierza używać zdalnej usługi musi posiadać oddzielną procedurę łącznikową (stub procedure) dla każdej procedury występującej w interfejsie usługi. Proces klienta musi ponadto posiadać moduł komunikacyjny (communication module). Rola procedury łącznikowej jest podobna do roli pośrednika (proxy) - jawi się ona (i tylko ona) klientowi jako lokalna procedura, ale zamiast wykonywać docelową operację, wykonuje serializację identyfikatora procedury i jej argumentów w komunikat żądania, który wysyła za pomocą swojego modułu komunikacyjnego do serwera. Kiedy nadchodzi komunikat odpowiedzi, wykonuje deserializację w celu odbioru rezultatu. Proces serwera zawiera moduł dyspozytora (dispatcher) wraz z oddzielną procedurą łącznikową serwera serwer stub procedure) i oddzielną procedurą usługową (service procedure) dla każdej procedury zdefiniowanej w interfejsie usługi. Dispatcher wybiera jedną z procedur łącznikowych serwera na podstawie identyfikatora procedury zawartego w komunikacie żądania. Procedura łącznikowa serwera (serwer stub procedure) jest podobna do metody szkieletowej, która odtwarza argumenty z komunikatu żądania, wywołuje odpowiednią procedurę usługową i marszrutuje zwracane wartości w komunikacie odpowiedzi. Procedury usługowe ( service procedure) implementują procedury zdefiniowane w interfejsie usługi. 38 / 1
RPC Proces budowy rozproszonej aplikacji Definicja struktur danych i interfejsu usługi dostarczanej przez serwer Generowanie szkieletu aplikacji z wykorzystaniem narzędzia rpcgen Uzupełnienie kodu procedur usługowych w celu spełnienia założeń funkcjonalnych rozproszonej aplikacji Kompilacja i łączenie programów klienta i serwera, wykonywane z wykorzystaniem narzędzia make 39 / 1
Przykład aplikacji RPC Definicja interfejsu usługi servera Rozproszona aplikacja obsługuje sklep z utworami muzycznymi. Struktury danych i interfejs usług definiuje następujący plik: 1 /* SKLEP.x: Sklep z utworami muz. */ 2 struct Utwor { 3 int Id; 4 string Autor <>; // tabl. o nieokreslonym rozmiarze 5 string Tytul <>; 6 float Cena ; 7 }; 8 9 program SKLEPPROG { 10 version SKLEPVERS { 11 int OtwarcieSklepu ( void ) = 1; 12 int DodanieUtworu ( struct Utwor ) = 2; 13 int IleUtworow ( void ) = 3; 14 struct Utwor PobranieUtworu ( int ) = 4; 15 int ZamkniecieSklepu ( void ) = 5; 16 } = 1; 17 } = 0 x20000001 ; 40 / 1
RPC Generator kodu rpcgen Na podstawie przygotowanego pliku definicyjnego można automatycznie wygenerować pliki źródłowe przykładowej aplikacji: 1 $ rpcgen -a -N sklep.x 2 $ ls 3 Makefile. sklep sklep.x sklep_clnt.c sklep_svc.c sklep.h sklep_client.c sklep_server.c Powstałe pliki reprezentują odpowiednio: sklep.h plik nagłówkowy z definicjami poszcz. identyfikatorów sklep_client.c szkielet aplikacji klienckiej sklep_server.c szkielet serwera sklep_clnt.c pieniek klienta sklep_svc.c pieniek serwera Makefile.sklep plik Makefile zarządzający kompilacją projektu. Kompilację najprościej jest przeprowadzić z wykorzystaniem programu make: 1 $ make -f Makefile. sklep 2 cc -g -c -o sklep_clnt.o sklep_clnt.c 3 cc -g -c -o sklep_client.o sklep_client.c 4 cc -g -o sklep_client sklep_clnt.o sklep_client.o -lnsl 5 cc -g -c -o sklep_svc.o sklep_svc.c cc -g -c -o sklep_server.o sklep_server.c 6 cc -g -o sklep_server sklep_svc.o sklep_server.o -lnsl W wyniku kompilacji powinny powstać m.in. dwa programy: sklep_client i sklep_server 41 / 1
Przykład aplikacji RPC Deklaracje podstawowe 1 /* Please do not edit this file. It was generated using rpcgen. */ 2 3 # ifndef _SKLEP_H_RPCGEN 4 # define _SKLEP_H_RPCGEN 5 6 # include <rpc / rpc.h> 7 8 # ifdef cplusplus 9 extern "C" { 10 # endif 11 12 struct Utwor { 13 int Id; 14 char * Autor ; 15 char * Tytul ; 16 float Cena ; 17 }; 18 typedef struct Utwor Utwor ; 19 20 # define SKLEPPROG 0 x20000001 21 # define SKLEPVERS 1 42 / 1
Przykład aplikacji RPC Deklaracje funkcji - wersja STDC i C++ 23 # if defined ( STDC ) defined ( cplusplus ) 24 # define OtwarcieSklepu 1 25 extern int * otwarciesklepu_1 ( CLIENT *); 26 extern int * otwarciesklepu_1_svc ( struct svc_req *); 27 # define DodanieUtworu 2 28 extern int * dodanieutwor_1 ( struct Utwor, CLIENT *); 29 extern int * dodanieutworu_1_svc ( struct Utwor, struct svc_req *); 30 # define IleUtworow 3 31 extern int * ileutworow_1 ( CLIENT *); 32 extern int * ileutworow_1_svc ( struct svc_req *); 33 # define PobranieUtworu 4 34 extern struct Utwor * pobranieutworu_1 ( int, CLIENT *); 35 extern struct Utwor * pobranieutworu_1_svc ( int, struct svc_req *); 36 # define ZamkniecieSklepu 5 37 extern int * zamknieciesklepu_1 ( CLIENT *); 38 extern int * zamknieciesklepu_1_svc ( struct svc_req *); 39 extern int sklepprog_1_freeresult ( SVCXPRT *, xdrproc_t, caddr_t ); 43 / 1
Przykład aplikacji RPC Deklaracje funkcji - wersja K&R C 41 # else /* K&R C */ 42 # define OtwarcieSklepu 1 43 extern int * otwarciesklepu_1 (); 44 extern int * otwarciesklepu_1_svc (); 45 # define DodanieUtworu 2 46 extern int * dodanieutworu_1 (); 47 extern int * dodanieutworu_1_svc (); 48 # define IleUtworow 3 49 extern int * ileutworow_1 (); 50 extern int * ileutworow_1_svc (); 51 # define PobranieUtworu 4 52 extern struct Utwor * pobranieutworu_1 (); 53 extern struct Utwor * pobranieutworu_1_svc (); 54 # define ZamkniecieSklepu 5 55 extern int * zamknieciesklepu_1 (); 56 extern int * zamknieciesklepu_1_svc (); 57 extern int sklepprog_1_freeresult (); 58 # endif /* K&R C */ 44 / 1
Przykład aplikacji RPC Plik XDR Plik XDR (ang. external Data Representation) jest generowany automatycznie przez rpcgen i zawiera procedury konwersji dla danych o typach zdefiniowanych przez użytkownika. 1 /* Please do not edit this file. It was generated using rpcgen. */ 2 3 # include " Sklep.h" 4 5 bool_t 6 xdr_utwor ( XDR *xdrs, Utwor * objp ) 7 { 8 register int32_t * buf ; 9 if (! xdr_int (xdrs, &objp ->Id )) 10 return FALSE ; 11 if (! xdr_string (xdrs, &objp ->Autor, ~0)) 12 return FALSE ; 13 if (! xdr_string (xdrs, &objp ->Tytul, ~0)) 14 return FALSE ; 15 if (! xdr_float (xdrs, &objp ->Cena )) 16 return FALSE ; 17 return TRUE ; 18 } 45 / 1
Przykład aplikacji RPC Kod serwera - lista utworów i funkcja pomocnicza Pierwsza część kodu serwera zawiera deklarację listy utworów, będącej zmienną globalną. Po niej następuje definicja funkcji pomocniczej, używanej do tworzenia nowego utworu. 7 # include " Sklep.h" 8 # include <glib.h> // naglowki bibl. GLib 9 10 11 GSList * utwory = NULL ; // lista utworow 12 13 Utwor * 14 nowy ( int id, char * autor, char * tytul, float cena ) { 15 Utwor * u = ( Utwor *) malloc ( sizeof ( Utwor )); 16 if (u) { 17 u->id = id; 18 u-> Autor = autor ; 19 u-> Tytul = tytul ; 20 u-> Cena = cena ; 21 } 22 return u; 23 } 46 / 1
Przykład aplikacji RPC Kod serwera - procedura inicjująca pracę serwera Tworzy ona dwa obiekty typu Utwor i dodaje je do listy utworów. W zastosowaniach praktycznych należałoby tu zrealizować np. odczyt listy utworów z pliku lub bazy danych. 25 int * 26 otwarciesklepu_1_svc ( struct svc_req * rqstp ) { 27 static int result ; 28 29 printf (" otwarciesklepu_1_svc ()...\ n"); 30 Utwor * u = nowy (1, "L.M.", " Scala ", 49.99); 31 if (u) utwory = g_slist_append ( utwory, u); 32 u = nowy (2, "S.M.", " Amtrak ", 99.99); 33 if (u) utwory = g_slist_append ( utwory, u); 34 printf (" Sklep ma %d utwory \n", g_slist_length ( utwory )); 35 36 return & result ; 37 } 47 / 1
Przykład aplikacji RPC Kod serwera - procedury użytkowe Przedstawione procedury użytkowe służą do dodania utworu do listy i odczytu ilości utworów. 39 int * 40 dodanieutworu_1_svc ( struct Utwor arg1, struct svc_req * rqstp ) { 41 static int result ; 42 43 printf (" dodanieutworu_1_svc ()...\ n"); 44 utwory = g_slist_append ( utwory, & arg1 ); 45 printf (" Sklep ma %d utwory \n", g_slist_length ( utwory )); 46 47 return & result ; 48 } 49 50 int * 51 ileutworow_1_svc ( struct svc_req * rqstp ) { 52 static int result ; 53 54 result = g_slist_length ( utwory ); 55 56 return & result ; 57 } 48 / 1
Przykład aplikacji RPC Kod serwera - procedura odczytu i końcowa Przedstawione procedury użytkowe służą do odczytu utworu z listy i zakończenia pracy serwera. 59 struct Utwor * 60 pobranieutworu_1_svc ( int arg1, struct svc_req * rqstp ) { 61 static struct Utwor result ; 62 63 result = *(( Utwor *) g_slist_nth_data (utwory, arg1 )); 64 65 return & result ; 66 } 67 68 int * 69 zamknieciesklepu_1_svc ( struct svc_req * rqstp ) { 70 static int result ; 71 72 // aktualizacja danych 73 // zwolnienie utworow zawartych w liscie 74 // zwolnienie listy 75 g_slist_free ( utwory ); 76 77 return & result ; 78 } 49 / 1
Przykład aplikacji RPC Kod klienta 1 /* This is sample code generated by rpcgen. */ 2 # include " Sklep.h" 3 4 void tostring ( Utwor * u) { 5 if (u) 6 printf ("Id (%d), Autor (%s), Tytul (%s), Cena (%5.2 f)\n", 7 u->id, u->autor, u->tytul, u->cena ); 8 } 9 10 void sklepprog_1 ( char * host ) { 11 CLIENT * clnt ; 12 int *result_1, *result_2, *result_3, * result_5 ; 13 struct Utwor dodanieutworu_1_arg1 ; 14 struct Utwor * result_4 ; 15 int pobranieutworu_1_arg1 = 1; 16 # ifndef DEBUG 17 clnt = clnt_create (host, SKLEPPROG, SKLEPVERS, " udp "); 18 if ( clnt == NULL ) { 19 clnt_pcreateerror ( host ); exit (1); 20 } 21 # endif /* DEBUG */ 50 / 1
Przykład aplikacji RPC Kod klienta 22 result_1 = otwarciesklepu_1 ( clnt ); 23 if ( result_1 == ( int *) NULL ) 24 clnt_perror (clnt, " call failed "); 25 dodanieutworu_1_arg1.id = 3; 26 dodanieutworu_1_arg1. Autor = " Chuck Mangione "; 27 dodanieutworu_1_arg1. Tytul = " Children of Sanchez "; 28 dodanieutworu_1_arg1. Cena = 9.90; 29 result_2 = dodanieutworu_1 ( dodanieutworu_1_arg1, clnt ); 30 if ( result_2 == ( int *) NULL ) 31 clnt_perror (clnt, " call failed "); 32 result_3 = ileutworow_1 ( clnt ); 33 if ( result_3 == ( int *) NULL ) 34 clnt_perror (clnt, " call failed "); 35 else 36 printf (" Sklep oferuje %d utwory \n", * result_3 ); 37 result_4 = pobranieutworu_1 ( pobranieutworu_1_arg1, clnt ); 38 if ( result_4 == ( struct Utwor *) NULL ) 39 clnt_perror (clnt, " call failed "); 40 else { 41 printf (" Utwor o indeksie %d to: ", pobranieutworu_1_arg1 ); 42 tostring ( result_4 ); 43 } 44 result_5 = zamknieciesklepu_1 ( clnt ); 45 if ( result_5 == ( int *) NULL ) { 46 clnt_perror (clnt, " call failed "); 47 } 51 / 1
Przykład aplikacji RPC Kod klienta 48 # ifndef DEBUG 49 clnt_destroy ( clnt ); 50 # endif /* DEBUG */ 51 } 52 53 int main ( int argc, char * argv []) 54 { 55 char * host ; 56 if ( argc < 2) { 57 printf (" usage : %s server_host \n", argv [0]); exit (1); } 58 host = argv [1]; 59 sklepprog_1 ( host ); 60 exit (0); 61 } 52 / 1
RMI - zdalne wywołanie metod Rozproszone obiekty Stan obiektu składa się z wartości jego pól. Zgodnie z paradygmatem obiektowym, stan całego programu jest reprezentowany przez oddzielne części, odpowiadające poszczególnym obiektom. Ponieważ obiektowo zorganizowane programy są w ten sposób logicznie podzielone, fizyczne rozłożenie obiektów pomiędzy różnymi procesami lub komputerami w systemie rozproszonym jest naturalnym rozszerzeniem. Rozproszone systemy obiektowe mogą być oparte na modelu klient-serwer. W tym przypadku obiekty są zarządzane przez serwery i ich klienci wywołują ich metody używając zdalnych wywołań metod. W RMI, żądanie klienta by wywołać metodę obiektu zostaje wysłane jako komunikat do serwera zawierającego obiekt. Wywołanie to jest realizowane przez metodę obiektu serwera, a rezultat jest zwracany do klienta w osobnym komunikacie. Ponieważ obiekty klientów i serwera są w różnych procesach, mamy do czynienia z naturalną enkapsulacją. To znaczy, że stan obiektu może być dostępny tylko za pomocą metod, co oznacza że nie jest możliwe by nieuprawnione metody (zewnętrzne) wpływały na stan. Pozwala to również odpowiednio rozwiązać zagadnienie równoczesnego dostępu, na przykład stosując mechanizmy synchronizacji (semafory, sekcje krytyczne). 53 / 1
RMI Rozproszony model obiektowy Obiekty które mogą podlegać zdalnym wywołaniom są nazywane zdalnymi obiektami. Aby umożliwić pracę ze zdalnymi obiektami wprowadzono szereg rozszerzeń modelu obiektowego. Każdy proces zawiera kolekcję obiektów, niektóre z nich mogą odbierać zarówno zdalne jak i lokalne wywołania metod, podczas gdy pozostałe mogą odbierać tylko lokalne wywołania, jak na schemacie poniżej. wywołania metod między obiektami w różnych procesach, na tym samym lub innym komputerze, są zdalne. wywołania metod między obiektami w tym samym procesie są lokalnymi wywołaniami metod. 54 / 1
RMI Rozproszony model obiektowy Zdalna referencja obiektu Identyfikator, który może być używany w systemie rozproszonym w celu odwołania się do wybranego unikalnego zdalnego obiektu. Reprezentacja jej jest zwykle różna od lokalnej referencji obiektu. Składa się ona z: adresu IP komputera i numeru portu związanego z procesem, który utworzył obiekt czasu utworzenia lokalnego numeru obiektu i identyfikatora interfejsu (nazwy) Zdalne referencje obiektu są podobne do lokalnych w tym że: aby zdalny obiekt mógł podlegać RMI musi być określony za pomocą zdalnej referencji obiektu zdalne referencje obiektów mogą być przekazane jako argumenty i zwracane jako rezultaty ze zdalnych wywołań metod Zdalny interfejs Interfejs, który deklaruje metody implementowane przez klasę zdalnego obiektu, na przykład jako publiczne metody w języku Java. Obiekty w innych procesach mogą wywoływać tylko te metody, które należą do zdalnego interfejsu, podczas gdy lokalne obiekty mogą wywoływać metody zdalnego interfejsu jak również inne publiczne metody implementowane przez zdalny obiekt. 55 / 1