II Klient-Serwer Komunikacja przy pomocy gniazd Gniazda pozwalają na efektywną wymianę danych pomiędzy procesami w systemie rozproszonym. Proces klienta Proces serwera gniazdko gniazdko protokół transportu operacje na gniazdach : funkcje czytania: read, recv, recvfrom ( czytanie buforowane blokujące ) funkcje pisania: write, send, sendto ( pisanie buforowane nieblokujące lub blokujące przy pełnym buforze) funkcja utworzenia gniazda: funkcja połączenia gniazda z adresem zdalnym: connect funkcja połączenia gniazda z adresem lokalnym: bind funkcja kontroli połączenia : accept adresowanie gniazda: adres komputera + numer portu numery portów: 1-1023 zastrzeżone 1024-5000 przydzielane przez system powyżej 5000 porty użytkownika
Rodzaje komunikacji 1) komunikacja połączeniowa procesy nawiązują ze sobą połączenie kontrolowane przez system. Po nawiązaniu połączenia dane są wysyłane lub odbierane w postaci strumienia bajtów ( protokół TCP ) operacje klienta: connect read, write, recv, send - łączy się z serwerem - czyta lub pisze do gniazdka operacje serwera: bind listen accept - łączy gniazdo ze swoim adresem sieciowym i numerem portu - ustala maksymalną listę żądań połączeń które mogą czekać na usługę (po przekroczeniu następuje odrzucenie) - przyjmuje żądanie połączenia od klienta i wysyła komunikat read, write, send, recv - czyta lub pisze do gnizdka uwagi: Dane są przesyłane w postaci bloku bajtów. Może się zdarzyć że aktualny blok bajtów nie może być przesłany w całości. Funkcje read/ write nie sygnalizują wtedy błędu. Należy je wywołać powtórnie aby wysłać lub odebrać pozostałą resztę bajtów. Dlatego odczyt lub zapis przez gniazdo należy zaimplementować w postaci iteracyjnego wywoływania funkcji read/ write.
2) komunikacja bezpołączeniowa (datagramowa) procesy nawiązują połączenie bez kontroli połączenia. Dane są wysyłane lub odbierane w postaci pakietów (protkół UDP ) operacje klienta: bind - adresuje gniazdko do odbioru danych sendto, recvfrom - pisze lub czyta z gniazdka operacje serwera: bind sendto, recvfrom - adresuje gniazdko do odbioru danych - czyta lub pisze do gniazdka uwagi: 1) Pakiet stracony. Proces nadawca nie ma wiedzy czy pakiet został odebrany przez proces odbiorcy. Pakiet może nigdy nie dotrzeć do odbiorcy z powodu błędu (suma kontrolna). Odbiorca powinien w takim przypadku ponowić żądanie wysłania pakietu. 2) Ograniczenie wielkości pakietów. Maksymalna wielkość pakietu zależy protokołu i konfiguracji sieci 3) Zbyt małe pakiety Pakiety mogą dotrzeć do odbiorcy w innej kolejności niż zostały wysłane. W przypadku wysyłania dużej ilości danych nadawca powinien numerować pakiety a odbiorca szeregować je we właściwej kolejności.
Ćwiczenia Pliki: KLIENT_SERWER/ bufor.h monitory.c monitory.h gniazdo.h gniazdo.c echo_icserwer.c echo_iclient.c echo_idserwer.c echo_idclient.c 1. Aplikacja klienet-serwer: Program klienta łączy się z serwerem i wysyła do niego tekst a serwer odpowiada w postaci echa a) echo_icserwer.c komunikacja połączeniowa b) echo_idserwer.c komunikacja bezpołączeniowa kompilacja: g++ gniazdo.c echo_icserwer.c -o serwer_echo 2. Utworzyć serwer współbieżny 3. Utworzyć serwer współbieżny działający w tle (jako demon) 4. Utworzyć serwer przesyłający klientowi czas systemowy. Zadania: 1) aplikacja klient-serwer: gra w kości dla dwóch klientów Klient nawiązują połączenie z serwerem i wysyła do niego parę liczb losowych z zakresu 1-6. Serwer odbiera liczby od dwóch klientów oblicza sumy i wysyła do klientów komunikat wygrałeś lub przegrałeś. Uwagi: możliwość rozszerzenia na więcej graczy 2) aplikacja klient-serwer: program producent-konsument Serwer pełni rolę bufora rozproszonego dla problemu producent konsument. Działa dwóch klientów, jeden wysyła dane do serwera (np. liczby losowe), serwer je odbiera i gromadzi w buforze (tablica ograniczona), a drugi klient odbiera te dane od serwera, które serwer mu odsyła pobierając je wcześniej z bufora. Uwagi: Serwer powinien mieć dwa wątki: jeden obsługuje klienta producenta, a drugi klienta konsumenta. Wątki serwera powinny wpisywać lub odczytywać dane z bufora korzystając z monitora (MONITOR_PK w pliku monitory.c) Możliwość rozszerzenia na wielu producentów i konsumentów
3) aplikacja klient-serwer: serwer jako baza danych Serwer zawiera bazę danych np tablicę liczb. Klienci mogą odczytywać dane z bazy jak i je zapisywać. Klient wysyła komunikat do serwera typu (czyta, klucz) lub (pisze, klucz). Klucz oznacza identyfikator rekordu danych (np. indeks elementu tablicy ). Serwer powinien zezwalać na czytanie wielu klientom jednocześnie. W przypadku gdy klient zapisuje coś do bazy pozostali klienci nie mogą mieć w tym momencie dostępu do bazy. Uwaga: Problem czytelników i pisarzy 4) Synchronizacja zegarów: Jest ustalona liczba procesów np. 3 Procesy działające w różnych systemach operacyjnych mają synchronizować własne zegary. Każdy proces ma dodatkowy wątek. Proces w pętli nieskończonej co jakiś czas aktualizuje swój czas lokalny na podstawie wartości uzyskanej przez jego wątek, następnie ten zaktualizowany czas wysyła komunikatem do losowo wybranego procesu i wypisuje go na ekranie. Odbieraniem komunikatów zajmuje się wątek procesu. Zapisuje on największy czas (np. tmax ) jaki odbierze w komunikacie. Czas lokalny tego procesu (np. t1) jest aktualizowany gdy t1 < tmax na nową wartość t1 = tmax+1 po czym nowy czas jest wysyłany do losowo wybranego procesu. Komunikacja jest przedstawiona na schemacie: P1 P2 P3 wątek1 wątek2 wątek3 Strzałki pokazują kto jest odbiorcą komunikatu. Należy użyć protokołu UDP. Uwaga: zegarem procesu może być licznik w pętli nieskończonej, a aktualizacja czasu następuje co pewną stałą liczbę cykli w pętli. Jako licznika można użyć zmiennej double 5) Serwer rozproszony: W systemie rozproszonym działa serwer główny i trzy serwery na innych maszynach. Procesy klienta wysyłają komunikat do serwera głównego który kieruje go do jednego z trzech wolnych serwerów. Odpowiedni serwer odsyła echo komunikatu do klienta. Działanie aplikacji pokazuje schemat: S1 S2 S2 komunikacja bezpołączeniowa (UDP) SERWER C1 C2 komunikacja z kontrolą połączenia (TCP)