Systemy rozproszone W. Bartkiewicz Uniwersytet Łódzki Katedra Informatyki W. Bartkiewicz Systemy rozproszone Wykład 10. Synchronizacja w systemach rozproszonych
Współbieżność 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
Synchronizacja zegarów fizycznych 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.
Synchronizacja zegarów fizycznych 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.
Relacja uprzedniości zdarzeń 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.
Relacja uprzedniości zdarzeń 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).
Relacja uprzedniości zdarzeń 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.
Znaczniki czasu Lamporta 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ę.
Znaczniki czasu Lamporta 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).
Wektorowe znaczniki czasu 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.
Wektorowe znaczniki czasu 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.
Wzajemne wykluczanie w warunkch rozproszonych 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.
Podejście scentralizowane 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.
Podejście scentralizowane 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.
Podejście scentralizowane 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
Algorytm Ricarta i Agrawali 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ń.
Algorytm Ricarta i Agrawali 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.
Algorytm Ricarta i Agrawali Katedra Informatyki Algorytm Ricarta i Agrawali
Algorytm pierścieniowy 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.
Wzajemne wykluczanie w warunkach rozproszonych 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.
Porównanie Katedra Informatyki 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.
Synchronizacja dostępu do całych plików w WIN32API Katedra Informatyki 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);
Synchronizacja dostępu do obszarów w pliku w WIN 32API Katedra Informatyki 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.
Blokada współdzielona obszaru w pliku w WIN32 API Katedra Informatyki //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 ...
Blokada wyłączna obszaru w pliku w WIN32 API Katedra Informatyki //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 ...
Serwery współbieżne Katedra Informatyki 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.
Serwery współbieżne Katedra Informatyki 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ą.
Gniazda – serwer jednowątkowy (1/3) Katedra Informatyki 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()); 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()); listen(sListen, 2);
Gniazda – serwer jednowątkowy (2/3) Katedra Informatyki 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;
Gniazda – serwer jednowątkowy (3/3) Katedra Informatyki 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; nLeft -= ret; idx += ret; return 0;
Gniazda – serwer wielowątkowy Katedra Informatyki 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()); CloseHandle(hThread);