400 likes | 578 Views
Uniwersytet Łódzki Katedra Informatyki. W. Bartkiewicz. Systemy rozproszone. Wykład 10. Synchronizacja w systemach rozproszonych. Katedra Informatyki. Współbieżność w systemach rozproszonych.
E N D
Uniwersytet Łódzki Katedra Informatyki W. Bartkiewicz Systemy rozproszone Wykład 10. Synchronizacja w systemach rozproszonych
Katedra Informatyki Współbieżność w systemach rozproszonych • Synchronizacja współbieżnych procesów w systemach rozproszonych stwarza szereg dodatkowych problemów. Najważniejsze z nich to: • Problem czasu w aplikacji rozproszonej. • Brak centralnej pamięci, dostępnej dla współbieżnych składników aplikacji, w której umieścić można mechanizmy synchronizacyjne. • Czas w aplikacji rozproszonej: • Co to znaczy, że dwa zdarzenia w aplikacji rozproszonej zachodzą współbieżnie? • Kiedy możemy powiedzieć, że jedno zdarzenie poprzedza drugie? • Brak centralnej pamięci: • W dotychczas rozważanych zagadnieniach synchronizacyjnych istniała wspólna pamięć, dostępna dla wątków (procesów) aplikacji, w której znajdowały się mechanizmy synchronizacyjne. • Zmienne globalne (wątki tego samego procesu), jądro systemu (wątki różnych procesów. • Gdy procesy pracują na różnych maszynach, takiej pamięci nie ma. Synchronizacja musi odbywać się więc poprzez operacje komunikacyjne.
Katedra Informatyki Komputer i czas • Układ odmierzający czas jest standardowym wyposażeniem większości dzisiejszych komputerów. • Z zegarem związane są zazwyczaj dwa rejestry: rejestr licznika (ang. counter) oraz rejestr podtrzymujący (ang. holding register). • Wraz z wybijaniem taktów przez czasomierz systemowy zmniejszana jest wartość rejestru licznika. • W momencie kiedy jego wartość dojdzie do zera wywoływane jest przerwanie i ładowana jest do niego wartość rejestru podtrzymującego. • Zauważmy, że zmieniając wartość rejestru podtrzymującego możemy sterować częstotliwością wywoływania przerwań, a tym samym częstotliwością tzw. impulsów zegara (ang clock tick).
Katedra Informatyki Czas uniwersalny • Do pomiaru upływu rzeczywistego czasu stosowano wiele różnych metod. • Jednym z bardziej przełomowych momentów w tej dziedzinie było wynalezienie zegara atomowego. Wtedy też na nowo zdefiniowano pojęcie sekundy jako liczbę przejść w atomie cezu 133. • Przy użyciu takich zegarów liczony jest międzynarodowy czas atomowy (ang. International Atomic Time –TAI), który ustanawiany jest poprzez uśrednienie pomiarów z różnych laboratoriów. • Okazało się, że mimo swoich zalet podejście to nie jest pozbawione błędu. • Ponieważ średni dzień słoneczny trwa coraz dłużej pojawia się rozbieżność z międzynarodowym czasem atomowym. • W celu rozwiązania tego problemu wprowadzono sekundy przestępne.
Katedra Informatyki Czas uniwersalny • System pomiaru czasu, który uwzględnia to ulepszenie nazywamy uniwersalnym czasem koordynowanym (ang. Universal Coordinated Time – UTC). • UTC jest podstawą dzisiejszego pomiaru czasu wśród cywilnych zastosowań. • W tym celu Narodowy Instytut Czasu Standardowego (ang. National Institute of Standard Time –NIST) posiada nadajnik radiowy o literach wywoławczych WWV, który co sekundę wysyła impuls czasu. • TAI + sekundy przestępne = UTC
Katedra Informatyki Synchronizacja zegarów fizycznych • Dopóki zegar jest używany lokalnie na jednej maszynie kwestia jego niedokładności nie jest aż taka istotna. • Procesy korzystają w tym wypadku z jednego zegara, a więc w ramach danej maszyny jego wskazania będą spójne. • Problem pojawia się gdy dostępnych mamy kilka maszyn, z których każda posiada swój własny zegar. • Ponieważ fizyczne zegary nie są idealne i ich częstotliwości będą się w jakimś stopniu różniły, po pewnym czasie zaczną wskazywać różne wartości. Innymi słowy pojawią się tzw. odchylenia wskazań zegara (ang. clock skew). • W takim przypadku założenie, że zegary fizyczne w danym momencie wskazują identyczny czas jest obarczone pewnym błędem • W praktyce mamy więc do czynienia z sytuacją, w której pewne zegary są szybsze, a inne wolniejsze. • Jeśli takie zegary mają stanowić podstawę uporządkowania czasowego zdarzeń w systemie rozproszonym, muszą być poddawane okresowej resynchronizacji.
Katedra Informatyki Synchronizacja zegarów fizycznych • Koordynacja czasu w środowisku rozproszonym zakłada zwykle, że jeden z węzłów jest serwerem czasu (ang. time server) wyznaczającym fizyczny czas całego systemu. • W procesie koordynacji czasu niezbędne jest uwzględnienie pewnych narzutów czasowych, związanych z działaniem serwera czasu, oraz operacjami komunikacyjnymi związanymi z propagacją informacji synchronizacyjnej. • Komputery środowiska rozproszonego muszą więc dokonywać różnego rodzaju korekt kompensujących wpływ powyższych opóźnień. • Opierają się one zazwyczaj na pomiarach czasu komunikacji (lub uśrednionych wynikach wielu pomiarów). • Osobnym problemem jest również kwestia kompensacji zegarów szybszych komputerów. • Zwykłe cofnięcie na czas nadesłany z serwera mogłoby spowodować, że czas płynie wstecz. • Zwykle kompensacja zegara polega na jego zwolnieniu do momentu, kiedy zostanie osiągnięta właściwa wartość czasu.
Katedra Informatyki Algorytm Cristiana • Algorytm przeznaczony jest głównie dla systemów, w których jedna maszyna jest serwerem czasu (np. posiada odbiornik WWV), a reszta maszyn jest z nią zsynchronizowana. • Okresowo każda maszyna wysyła komunikat do serwera czasu, pytając o bieżący czas. Serwer odsyła, jak szybko może wiadomość ze swoim aktualnym czasem. • Koszty komunikacji i przetwarzania zapytania przez serwer powodują oczywiście, że wartość czasu UTC wysłana przez serwer, po dotarciu do nadawcy zapytania staje się już nieaktualna. • Gdy nadawca otrzyma odpowiedź od serwera może: • Ustawić swój zegar na czas serwera. • Powiększyć czas z wiadomości o czas komunikacji z serwerem. • Zastosować dodatkowo czas przetwarzania zapytania przez serwer.
Katedra Informatyki Algorytm z Berkeley • Serwer czasu jest aktywny. Serwer czasu okresowo wypytuje każdą maszynę, aby poznać jej czas. • Na podstawie odpowiedzi serwer wylicza średni czas i wysyła komunikaty do innych maszyn, aby odpowiednio zmieniły swój czas lub zwolniły zegar do momentu, aż zostanie osiągnięta właściwa jego wartość. • Takie podejście do synchronizacji zegarów fizycznych może być użyte m.in. W systemie, w którym nie ma specjalnych serwerów czasu, a jedynie chodzi o wspólne ustalenie jednej podstawy czasu.
Katedra Informatyki Algorytm uśredniania • Algorytm uśredniania jest algorytmem zdecentralizowanym. • W podejściu tym czas dzieli się na przedziały o określonym i stałym rozmiarze. • Na początku każdego przedziału następuje resynchronizacja wszystkich zegarów. • Polega to na tym, że każda maszyna rozgłasza wtedy aktualny czas swojego zegara. • W momencie wysłania maszyna uruchamia lokalny czasomierz i rozpoczyna zbieranie komunikatów od innych maszyn. • Komunikaty rozgłoszeniowe mogą przychodzić w różnych chwilach od różnych nadawców. • Gdy zostaną zebrane wszystkie odpowiedzi obliczana jest na ich podstawia nowa wartość czasu. • W najprostszym przypadku wykorzystuje się uśrednianie. • W zmodyfikowanej wersji odrzuca się dodatkowo przed uśrednianiem skrajne wartości, aby nie zaburzały zbytnio wyniku. • W jeszcze bardziej rozbudowanej wersji są brane pod uwagę m.in. czasy przesyłania komunikatów.
Katedra Informatyki Relacja uprzedniości zdarzeń • Problem określania fizycznego czasu występowania poszczególnych zdarzeń w systemie rozproszonym jest więc zadaniem dosyć złożonym. • Pomiar czasu jest prosty na jednym komputerze. • Nie potrafimy często jednak ściśle synchronizować zegarów różnych maszyn w systemie rozproszonym. • Analizując zagadnienie synchronizacji działania systemu rozproszonego, możemy jednak zauważyć, że do realizacji tego zadania nie potrzebujemy aż tak silnego instrumentu, jak ścisłe i precyzyjne określanie czasu. • Do tego celu wystarczy nam, jeśli będziemy mogli uporządkować kolejność występowania zdarzeń w systemie, tzw. relację uprzedniości zdarzeń (ang. happened before relation) (Lamport) • Uporządkowanie opiera się na dwu prostych i intuicyjnych przesłankach: • Jeśli dwa zdarzenia występują w tym samym procesie, to ich kolejność uporządkować możemy jednoznacznie zgodnie z czasem wskazywanym przez lokalny zegar komputera na którym działa proces. • Przy każdym przesłaniu komunikatu między procesami, zdarzenie jego wysłania poprzedza zdarzenie jego odbioru.
Katedra Informatyki Relacja uprzedniości zdarzeń • Niech Eik oznacza zdarzenie, które było k-tym zdarzeniem procesu Pi. Zdarzenie Eik poprzedza Ejl w sensie relacji uprzedniości zdarzeń jeśli: • Zdarzenia działają wewnątrz tego samego procesu oraz Eik występuje przed Ejl (tzn. i = j oraz k < l). • Jeśli i ≠ j, to Eik jest zdarzeniem wysłania pewnej wiadomości m przez proces Pi, a Ejl jest zdarzeniem odebrania tej samej wiadomości m przez proces Pj. • Istnieje sekwencja zdarzeń E0 E1 … En, taka że E0 = Eik, En = Ejl i dla każdej pary (Ex, E(x+1)), gdzie 0 ≤ x ≤ n – 1 zachodzi 1) lub 2), tzn. gdy istnieje sekwencja zdarzeń rozpoczynająca się od zdarzenia Eik i kończąca zdarzeniem Ejl, taka że dla każdej pary kolejnych zdarzeń zachodzi jedna z dwóch wcześniej opisanych sytuacji. • Relacja uprzedniości zdarzeń jest relacją antysymetryczną oraz przechodnią, a tym samym jest relacją częściowego porządku. • Jeżeli między dwoma zdarzeniami nie zachodzi relacja uprzedniości, mówimy o nich, że są współbieżne (ang. concurrent).
Katedra Informatyki Relacja uprzedniości zdarzeń • Zdarzenie E24 poprzedza E25 (na mocy punktu 1). • Zdarzenie E21 poprzedza E13 (na mocy punktu 2). • Zdarzenie E11 poprzedza E39 (na mocy punktu 3). • Zdarzenia E11 i E37 są współbieżne.
Katedra Informatyki Znaczniki czasu Lamporta • Jednym ze sposobów pomiaru czasu logicznego, wykorzystującym bezpośrednio relację uprzedniości zdarzeń są znaczniki czasu Lamporta. • Każdy proces utrzymuje monotonicznie rosnący licznik programowy, pełniący funkcje zegara logicznego (jego wartość zazwyczaj nie pozostaje w żadnym związku z jakimkolwiek zegarem fizycznym). • Zegar logiczny służy do przypisana każdemu zdarzeniu a pewnego znacznika czasu C(a). • Algorytm aktualizacji zegara logicznego procesu: • Każdy proces Pi zwiększa wartość swojego zegara Ci pomiędzy zdarzeniami o pewną wartość całkowitą (zazwyczaj 1). • Jeżeli a jest zdarzeniem wysłania wiadomości m przez proces Pi do procesu Pj, wtedy wiadomość m zawiera znacznik czasu Tm = Ci(a). W momencie otrzymania wiadomości m proces Pj ustawia: Cj = max(Cj, Tm) + 1 (lub zwiększa licznik o dowolną stałą dodatnią). • Jeżeli weźmiemy dwa zdarzenia a i b, przy czym a poprzedza zdarzenie b, to zachodzi nierówność C(a) < C(b). Należy pamiętać, że implikacja taka nie zachodzi w odwrotną stronę.
Katedra Informatyki Znaczniki czasu Lamporta • Rozszerzeniem tego algorytmu jest dodanie po przecinku do każdej wartości zegara np. numeru procesu. • Robi się tak, gdyż w normalnych warunkach dwa zdarzenia mogłyby posiadać znaczniki czasowe o tej samej wartości. • Dodatkowa informacja pozwala na rozróżnienie zdarzeń. • Na przykład zdarzenie, które wystąpiło w procesie 4 w chwili gdy wartość jego zegara logicznego wynosiła 120, będzie oznaczone znacznikiem (120, 4).
Katedra Informatyki Wektorowe znaczniki czasu • Wektorowe znaczniki czasu (ang. vector timestamps) są rozwinięciem koncepcji znaczników czasu Lamporta. • Znaczniki czasu Lamporta pozwalają na całkowite uporządkowanie zdarzeń w systemie rozproszonym, ale na ich podstawie nie możemy stwierdzić jaki był związek między zdarzeniami. • Każdy proces jest wyposażony w zegar Vi, który jest wektorem liczb całkowitych i ma długość n, równą liczbie procesów w systemie. • Wartość Vi[i] (znana przez proces Pi) jest równa liczbie zdarzeń jakie zaszły do tej pory w procesie Pi. • Vi[j] dla j różnego od i jest wartością zegara procesu Pj, jaką zna proces Pi. Innymi słowy, w dowolnym momencie czasu, j-ty element Vi wskazuje czas pojawienia się ostatniego zdarzenia procesu Pj, które poprzedza (w sensie relacji uprzedniości zdarzeń) bieżący moment czasu w procesie Pi.
Katedra Informatyki Wektorowe znaczniki czasu • Wektorowe znaczniki czasu przekazywane są razem z komunikatami. • W ten sposób odbiorca jest powiadamiany o liczbie zdarzeń, które wystąpiły u nadawcy oraz u innych procesów, o których wiedział nadawca zanim wysłał komunikat. • Po tym jak proces Pi otrzymuje od innego procesu Pj wektor v, aktualizuje własny ustawiając każdy wpis Vi[k] na wartość maksimum{Vi[k], v[k]}. • Jeżeli w systemie wykorzystującym zegary wektorowe zdarzenie a poprzedza przyczynowo zdarzenie b, to zachodzi własność V(a) < V(b) (tzn. V(a)[k] < V(b)[k] dla wszystkich k). • Co ważniejsze implikacja ta jest również prawdziwa w odwrotnym wypadku, czyli jeżeli wartość znacznika wektorowego dla zdarzenia a jest większa od znacznika zdarzenia b, to prawdą jest to, iż a poprzedza b.
Katedra Informatyki Wzajemne wykluczanie w warunkch rozproszonych • Brak dostępnej dla wszystkich procesów centralnej pamięci, w której można umieścić mechanizmy synchronizacyjne. • Procesy zajmujące zasoby przed wykonaniem strefy krytycznej muszą więc, w celu zapewnienia wzajemnego wykluczania synchronizować się wzajemnie, przesyłając odpowiednie komunikaty. • Problem rozproszonego wzajemnego wykluczania (distributed mutual exclusion). • Kilka typów algorytmów: • Podejście scentralizowane • Algorytmy rozproszone oparte na rozsyłaniu grupowym: np. algorytm Lamporta, algorytm Ricarta i Agrawali. • Algorytm pierścieniowy z żetonem.
Katedra Informatyki Podejście scentralizowane • W podejściu scentralizowanym eden proces jest pełni funkcję koordynatora: • Proces P, który chce wejść do sekcji krytycznej wysyła wiadomość do koordynatora. • Jeżeli żaden inny proces nie przebywa aktualnie w sekcji krytycznej, ani nie żąda do niej dostępu, koordynator odsyła do P komunikat z pozwoleniem. • Po otrzymaniu pozwolenia P wchodzi do sekcji krytycznej • Jeżeli w tym samym czasie do tej samej sekcji krytycznej chce się dostać inny proces, koordynator po prostu wstrzymuje się z odpowiedzią, blokując w ten sposób proces, który czeka na odpowiedź. Ewentualnie może np. odesłać odpowiedź z odmową wejścia do sekcji krytycznej. • Wychodząc ze strefy krytycznej proces informuje o tym koordynatora, który może np. wysłać komunikat o udostępnieniu strefy krytycznej kolejnemu procesowi. • Jeśli koordynator blokuje procesy żądające dostępu do strefy krytycznej, musi w sposób „sprawiedliwy” kolejkować otrzymywane od nich żądania dostępu.
Katedra Informatyki Podejście scentralizowane • Algorytm ten posiada kilka istotnych własności. • Zapewnia właściwości bezpieczeństwa. • Jest sprawiedliwy w tym sensie, że procesy są obsługiwane są zgodnie z kolejnością żądań. • Dodatkowo każdy z procesów w końcu, będzie mógł uruchomić swoją sekcję krytyczną. Innymi słowy algorytm nie powoduje zagłodzenia. • Algorytm jest prosty w implementacji. Proces wejścia do sekcji krytycznej wymaga przesłania tylko trzech komunikatów. • Wadą algorytmu jest scentralizowany koordynator: • podatność na awarie, • może stać się wąskim gardłem wydajności.
Katedra Informatyki Podejście scentralizowane
Katedra Informatyki Algorytm Lamporta • Algorytm Lamporta jest rozproszonym algorytmem synchronizacji wzajemnego wykluczania, wykorzystującym znaczniki czasu Lamporta. • Każdy proces przechowuje kolejkę żądań sekcji krytycznej uszeregowanych według znaczników czasowych • Algorytm ten wymaga, aby wiadomości dostarczane były pomiędzy każdą parą procesów w kolejności FIFO. • Gdy proces Pi zamierza wejść do sekcji krytycznej, • Rozgłasza do wszystkich procesów komunikat z żądaniem dostępu, ze znacznikiem czasu (ts(i), i). • Następnie umieszcza żądanie w swojej kolejce żądań. • Gdy proces Pj otrzyma żądanie od procesu Pi, odsyła ODPOWIEDŹ oznaczoną znacznikiem czasowym do procesu Pi i umieszcza żądanie procesu Pi w swojej kolejce żądań.
Katedra Informatyki Algorytm Lamporta • Proces Pi rozpoczyna wykonywanie sekcji krytycznej, gdy spełnione są dwa następujące warunki: • Pi otrzymał od wszystkich innych procesów wiadomość ze znacznikiem czasowym większym niż znacznik żądania (ts(i), i). • Żądanie procesu Pi jest na początku jego własnej kolejki żądań. • Po wyjściu z sekcji krytycznej • Proces Pi usuwa żądanie ze swojej kolejki żądań i rozgłasza do wszystkich procesów oznaczony znacznikiem czasu komunikat ZWOLNIJ. • Jeżeli proces Pj otrzyma wiadomość zwolnij od procesu Pi, usuwa żądanie Pi ze swojej kolejki żądań. • Kiedy proces usuwa pewne żądanie ze swojej kolejki żądań, jego własne żądanie może pojawić się na początku kolejki, umożliwiając mu wejście do sekcji krytycznej. • Algorytm wykonuje żądania wejścia do sekcji krytycznej w rosnącym porządku i zgodnie z ich znacznikami czasowymi.
Katedra Informatyki Algorytm Lamporta
Katedra Informatyki Algorytm Ricarta i Agrawali • Algorytm Ricarta i Agrawali jest zoptymalizowana wersją algorytmu Lamporta, która obywa się bez komunikatu ZWOLNIJ (sekcję krytyczną) poprzez połączenie ich z komunikatami typu ODPOWIEDŹ. • Gdy proces Pi chce wejść do sekcji krytycznej: • Rozgłasza do wszystkich procesów komunikat z żądaniem, oznaczony znacznikiem czasu. • Czeka, aż otrzyma wiadomości ODPOWIEDŹ od wszystkich procesów, dopiero potem wchodzi do strefy krytycznej. • W momencie gdy proces Pj otrzyma żądanie od procesu Pi, wysyła odpowiedź do procesu Pi pod warunkiem, że nie zachodzi jedna z następujących sytuacji: • Proces Pj sam wykonuje sekcję krytyczną, • Proces Pj sam żąda wykonania sekcji krytycznej, a znacznik czasowy jego żądania jest mniejszy niż znacznik czasowy żądania procesu Pi. • Jeżeli zachodzi, któraś z tych dwóch sytuacji, żądanie procesu Pi jest blokowane tzn. Pj nie odpowiada na nie i trafia ono do przechowywanej przez niego kolejki żądań.
Katedra Informatyki Algorytm Ricarta i Agrawali • W chwili kiedy proces Pi kończy wykonywanie sekcji krytycznej wysyła odpowiedzi na wszystkie przechowywane w swojej kolejce żądania, a następnie usuwa je z kolejki. • Odpowiedzi na żądania procesu blokowane są tylko przez procesy, które ubiegają się o wejście do sekcji krytycznej i mają wyższy priorytet tzn. mniejszy znacznik czasowy. • W ten sposób, gdy proces odsyła wiadomość typu odpowiedź na wszystkie odroczone żądania, proces, który ma kolejny najwyższy priorytet żądania, otrzymuje ostatnią niezbędną odpowiedź i wchodzi do sekcji krytycznej. • Innymi słowy sekcje krytyczne w algorytmie Ricarta i Agrawali wykonywane są w kolejności zgodnej z wartościami znaczników czasowych ich żądań. • Algorytmy rozproszone mino swojej pozornej atrakcyjności są wolniejsze, bardziej skomplikowane, i mniej odporne na awarie od podejścia scentralizowanego. • Wejście do strefy krytycznej wymaga pozwolenia od wszystkich procesów, a nie jednego koordynatora. Wystarczy np. awaria któregokolwiek z nich.
Katedra Informatyki Algorytm Ricarta i Agrawali
Katedra Informatyki Algorytm pierścieniowy • Procesy zorganizowane są w pierścień logiczny, którego topologia zazwyczaj niezależna jest od fizycznych powiązań między komputerami. • Każdy proces musi znać wyłącznie połączenie komunikacyjne do kolejnego sąsiada. • Po pierścieniu krąży (w jednym kierunku) żeton, komunikat zezwalający procesowi na wejście do strefy krytycznej. • Jeśli proces, który nie chce wchodzić do strefy krytycznej, otrzyma żeton, natychmiast przekazuje go dalej do sąsiada. • Jeśli proces chce wejść do strefy krytycznej, to czeka aż dotrze do niego żeton, zachowując go przez cały okres pobytu w strefie krytycznej. Po jej opuszczeniu, przekazuje żeton sąsiadowi. • Problem z rekonfiguracją pierścienia w razie awarii procesu, oraz z odtwarzaniem zagubionych żetonów.
Katedra Informatyki Wzajemne wykluczanie w warunkach rozproszonych • Jak widzimy algorytmy synchronizacji zajmowania zasobów przez procesy w systemie rozproszonym pozostawiają wiele do życzenia, zwłaszcza pod względem efektywności oraz odporności na awarie. • W praktyce stosowane są więc one raczej jako pewna ostateczność. • W większości praktycznych rozwiązań procesy w systemie rozproszonym nie zajmują same zasobów. • Mogą korzystać z dostępnego za pośrednictwem sieci współdzielonego systemu plików (serwera plików). Procesy zgłaszają do niego zamówienia zajęcia zasobów (danych) wywołując odpowiednie operacje dostępowe i synchronizowane są poprzez mechanizmy wbudowane w te operacje. • Zasoby zajmuje wyspecjalizowana aplikacja serwera. Procesy zgłaszają do niego jedynie zamówienia na wykonanie określonych operacji jako klienty. Każdy z klientów reprezentowany jest w serwerze poprzez współbieżnie działający proces lub wątek wykonujący operacje na zasobach. Synchronizacja dostępu do nich odbywa się więc na komputerze serwera np. z wykorzystaniem omawianych wcześniej mechanizmów blokowania.
Katedra Informatyki Porównanie • W przypadku zarządzania danymi, synchronizacja może odbywać się: • Za pośrednictwem usług dostępu do plików (lub obszarów w plikach) oferowanych przez zdalne (lub lokalne) systemy plików. • Każdy klient bezpośrednio działa na plikach danych (podejście desktopowe). • Każdy klient otrzymuje tylko informacje o możliwości lub odmowie dostępu do zasobu i samodzielnie zarządza powstającymi sytuacjami wyjątkowymi. • Za pośrednictwem aplikacji serwerowej. • Klienci działają na danych pośrednio, za pomocą aplikacji serwerowej, która (między innymi) synchronizuje dostęp do zasobów. • Klienci nie muszą zarządzać konfliktami dostępu i wynikającymi z nich blokadami. • Aplikacja serwerowa musi być przygotowana do jednoczesnej (współbieżnej) obsługi wielu klientów – serwery wieloprocesowe lub wielowątkowe. • Rozwiązanie to jest konieczne przy bardziej skomplikowanych operacjach na danych, związanych np. z obsługą transakcji.
Katedra Informatyki Synchronizacja dostępu do całych plików w WIN32API HANDLE CreateFile( LPCTSTR lpFileName, // Nazwa pliku DWORD dwDesiredAccess, // Tryb dostępu DWORD dwShareMode, // Tryb współdzielenia LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD DWORD dwCreationDisposition, // Sposób tworzenia DWORD dwFlagsAndAttributes, // Atrybuty pliku HANDLE hTemplateFile // Uchwyt do szablonu pliku ); Przykład: HANDLE hFile = CreateFile("place.dat", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Katedra Informatyki Synchronizacja dostępu do obszarów w pliku w WIN 32API BOOL LockFileEx( HANDLE hFile, // Uchwyt do pliku DWORD dwFlags, // Opcje blokady DWORD dwReserved, // Zarezerwowany, musi być 0 DWORD nNumberOfBytesToLockLow, // Dolne słowo długości obszaru DWORD nNumberOfBytesToLockHigh, // Górne słowo długości obszaru LPOVERLAPPED lpOverlapped // Zawiera ofset początku obszaru ); BOOL UnlockFileEx( HANDLE hFile, // handle to file DWORD dwReserved, // reserved DWORD nNumberOfBytesToUnlockLow, // low-order part of length DWORD nNumberOfBytesToUnlockHigh, // high-order part of length LPOVERLAPPED lpOverlapped // unlock region start ); Użycie w opcjach blokady stałej LOCKFILE_EXCLUSIVE_LOCK powoduje założenie blokady w trybie wyłączności w przeciwnym przypadku blokada jest współdzielona.
Katedra Informatyki Blokada współdzielona obszaru w pliku w WIN32 API //Zakładamy, że plik jest otwarty bez blokad i ustawiony przed właściwym // rekordem int result = 0; OVERLAPPED ovr; struct dane bufor; //pobranie bieżącej pozycji w pliku long filePtr = SetFilePointer(hFile, 0, NULL, FILE_CURRENT); ovr.OffsetHigh = 0; ovr.Offset = filePtr; ovr.hEvent = 0; result = LockFileEx(hFile, LOCKFILE_FAIL_IMMEDIATELY, 0, sizeof(struct dane), 0, &ovr); if ( result ) { ReadFile(hFile, &bufor, sizeof(bufor), &nBytesRead, NULL); UnlockFileEx(hFile, 0, sizeof(struct dane), 0, &ovr); } else { // zablokowany w trybie exclusive ... }
Katedra Informatyki Blokada wyłączna obszaru w pliku w WIN32 API //Zakładamy, że plik jest otwarty bez blokad i ustawiony przed właściwym // rekordem int result = 0; OVERLAPPED ovr; struct dane bufor; //pobranie bieżącej pozycji w pliku long filePtr = SetFilePointer(hFile, 0, NULL, FILE_CURRENT); ovr.OffsetHigh = 0; ovr.Offset = filePtr; ovr.hEvent = 0; result = LockFileEx(hFile, LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK, 0, sizeof(struct dane), 0, &ovr); if ( result ) { WriteFile(hFile, &bufor, sizeof(bufor), &nBytesWrite, NULL) ; UnlockFileEx(hFile, 0, sizeof(struct dane), 0, &ovr); } else { // zablokowany w trybie shared lub exclusive ... }
Katedra Informatyki Serwery współbieżne • W podejściu klient/serwer, zarządzaniem współdzielonymi zasobami zajmuje się specjalna aplikacja serwerowa. • Każdy klient żądający dostępu do zasobu, obsługiwany jest zazwyczaj przez odrębny wątek lub proces tej aplikacji. Możliwe są przy tym dwa podejścia. • Po zgłoszeniu się na nasłuchowym punkcie końcowym nowego klienta, tworzony jest mowy wątek (proces), który będzie się z nim dalej komunikował i realizował jego zamówienia. Z chwilą zakończenia dialogu z klientem, wątek (proces) jest kończony. • Aplikacja serwerowa tworzy na początku swego działania pulę wątków (procesów), zawieszając chwilowo ich działanie. Po zgłoszeniu się na nasłuchowym punkcie końcowym nowego klienta, jeden z wątków (procesów) jest odwieszany i obsługuje zamówienia tego klienta. Z chwilą zakończenia dialogu, wątek (proces) jest ponownie zawieszany.
Katedra Informatyki Serwery współbieżne • Tworzenie i kończenie wątków (a zwłaszcza procesów) wymaga pewnych nakładów. W związku z tym rozwiązanie to stosowane jest w przypadku gdy dialog z użytkownikiem jest bardziej złożony, a realizacja zamówień bardziej obciążająca. • W przypadku krótkich i prostych zamówień wielu użytkowników, zwłaszcza w serwerach bezstanowych, stosuje się rozwiązanie oparte na puli wątków (procesów). • Podejście oparte na puli wątków wymaga określenia z góry maksymalnej liczby współbieżnych użytkowników, którzy obsługiwani będą jednocześnie przez aplikację serwerową.
Katedra Informatyki Gniazda – serwer jednowątkowy (1/3) int main(int argc, char **argv){ WSADATA wsd; SOCKET sListen, sClient; int iAddrSize; struct sockaddr_in local, client; if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { printf("Nie mozna wczytac biblioteki Winsock!\n"); return 1; } sListen = socket(AF_INET, SOCK_STREAM, 0); if (sListen == INVALID_SOCKET) { printf("Funckja socket() zakonczona bledem: %d\n", WSAGetLastError()); return 1; } local.sin_addr.s_addr = htonl(INADDR_ANY); local.sin_family = AF_INET; local.sin_port = htons(5150); //port na ktorym nasluchujemy if (bind(sListen, (struct sockaddr *)&local, sizeof(local)) == SOCKET_ERROR) { printf("Funkcja bind() zakonczona bledem: %d\n", WSAGetLastError()); return 1; } listen(sListen, 2);
Katedra Informatyki Gniazda – serwer jednowątkowy (2/3) int main(int argc, char **argv){ ... printf("Serwer wystartowal\n"); while (1) { iAddrSize = sizeof(client); sClient = accept(sListen, (struct sockaddr *)&client, &iAddrSize); if (sClient == INVALID_SOCKET) { printf("Funkcja accept() zakonczona bledem: %d\n", WSAGetLastError()); break; } printf("Zaakceptowano klienta: %s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); ClientService(sClient); } closesocket(sListen); WSACleanup(); return 0; }
Katedra Informatyki Gniazda – serwer jednowątkowy (3/3) DWORD WINAPI ClientService(SOCKET sock) { char szBuff[DEFAULT_BUFFER]; int ret, nLeft, idx; while(1) { ret = recv(sock, szBuff, DEFAULT_BUFFER, 0); if (ret == 0) break; // Zamknięcie uzgodnione else if (ret == SOCKET_ERROR) { printf("Blad: %d\n",WSAGetLastError()); break; } szBuff[ret] = '\0'; printf("ODBIOR: '%s'\n", szBuff); nLeft = ret; idx = 0; while(nLeft > 0) { ret = send(sock, &szBuff[idx], nLeft, 0); if (ret == 0) break; else if (ret == SOCKET_ERROR) { printf("Blad: %d\n",WSAGetLastError()); break; } nLeft -= ret; idx += ret; } } return 0; }
Katedra Informatyki Gniazda – serwer wielowątkowy while (1) { iAddrSize = sizeof(client); sClient = accept(sListen, (struct sockaddr *)&client, &iAddrSize); if (sClient == INVALID_SOCKET) { printf("Funkcja accept() zakonczona bledem: %d\n", WSAGetLastError()); break; } printf("Zaakceptowano klienta: %s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sClient, 0, &dwThreadId); if (hThread == NULL) { printf("Funkcja CreateThread() zakonczona bledem: %d\n", GetLastError()); break; } CloseHandle(hThread); }