Opis zadania

Zadaniem jest modyfikacja napisanego poprzednio programu do licytacji cen wielu przedmiotów przez wielu agentów na wersję klient-serwer. Programy klientów powinny komunikować się z serwerem przez gniazdka domeny Unix PF_UNIX (opcjonalnie: gniazdka internetowe PF_INET). Komunikacja powinna być bezpołączeniowa SOCK_DGRAM.

Program z poprzedniego zadania należy przerobić na dwa oddzielne programy. Jeden z nich nazwiemy serwerem i jego zadaniem będzie odbieranie podbić licytacji i rejestrowanie ich w tablicach Bids i NBids. Drugi program nazwiemy klientem, i jego zadaniem będzie stworzenie odpowiedniej liczby podprocesów agentów, którzy będą generowali podbicia licytacji i wysyłali je serwerowi. Każde pojedyncze pobicie wysyłane jest pojedynczym komunikatem.

Program serwera należy zrealizować jako serwer iteracyjny, który po odebraniu każdej oferty podbicia zwiększy cenę odpowiedniego przedmiotu, i wróci do oczekiwania na dalsze oferty.

Uwaga: w tym zadaniu liczenie ofert jest jednowątkowe i nie ma żadnej współdzielonej struktury danych. Zatem cały kod synchronizacji muteksami z poprzedniego zadania nie będzie tu potrzebny i należy go usunąć. Natomiast proszę pozostawić pomiar czasu działania programów (rzeczywistego i wirtualnego), zarówno serwera jak i klienta, dla porównania z poprzednią wersją programu.

Zadanie należy zrealizować przy użyciu gniazdek domeny UNIX z adresami wykorzystującymi ścieżkę (lub nazwę) dowolnego istniejącego i dostępnego dla użytkownika pliku.

Wykonanie ćwiczenia

Ćwiczenie należy wykonać w dwóch etapach. W pierwszym etapie celem będzie poprawne wysyłanie komunikatów od podbiciach przez klienta(ów), i odbieranie ich przez serwer. Pojedynczy komunikat powinien zawierać numer przedmiotu i wartość podbicia. Obie dane w programie są wartościami typu int, typowo zajmującymi cztery bajty. Komunikat może te wartości przechowywać w formacie binarnym (jako osiem bajtów), lub w formacie tekstowym, w postaci sformatowanego stringa.

W drugim etapie celem jest śledzenie przez serwer całego przebiegu licytacji do końca, z wyświetlaniem wyników końcowych i sum kontrolnych na wyjściu.

Jednak chcemy przyjąć założenie, że serwer licytacji nie ma informacji o klientach biorących udział w licytacji (liczbie agentów oraz liczbie podbić licytacji, które oni wykonają), i pracuje oraz przyjmuje zgłoszenia w pętli nieskończonej (ale może być zakończony poleceniem użytkownika lub innego programu).

W tej sytuacji pojawia się problem zakończenia licytacji i wyświetlenia wyników. Istnieje szereg możliwych sposobów rozwiązania tego problemu.

  1. Najprostsze rozwiązanie polega na odrzuceniu założenia o nieznajomości liczby agentów i podbić, i prostym liczeniu otrzymanych komunikatów przez serwer. W tej sytuacji powinien on przejść do wyświetlenia wyników końcowych i podsumowania po otrzymaniu N_AGENTS*BIDDING_ROUNDS komunikatów.

    Jednak to rozwiązanie nie zadziała poprawnie gdyby część komunikatów z jakiegoś powodu została zgubiona. Łamie ono również przyjęte założenie, że serwer nie wie ilu jest klientów i ile podbić wykonają. Dlatego możemy traktować to rozwiązanie jako tymczasowe, i najlepiej byłoby w dalszym ciągu zastąpić je jednym z następujących.

  2. Dość prostym rozwiązaniem zachowującym założenie o nieznajomości liczby agentów i podbić przez serwer byłoby wysłanie specjalnego komunikatu (znacznika) na zakończenie licytacji. Proces główny klienta, po zakończeniu pracy ostatniego podprocesu, mógłby wysłać taki komunikat. Na przykład, mógłby on zawierać numer przedmiotu spoza dozwolonego zakresu (-1 albo 40). Serwer zinterpretowałby otrzymanie takiego komunikatu jako sygnał zakończenia licytacji, wyświetlił wyniki końcowe i podsumowaniu, i sam zakończyłby pracę.

  3. Inną możliwością jest wykorzystanie opóźnień czasowych. Ponieważ zakładamy, że licytacja przebiega w czasie rzeczywistym, i agenci przysyłają swoje podbicia bez żadnych opóźnień, serwer mógłby oczekiwać na każde zgłoszenie tylko przez jakiś czas (np. 0.1 sekundy). Jeśli w takim czasie nie przyjdzie żaden nowy komunikat podbicia, to serwer może uznać, że wszyscy agenci skończyli pracę i sam też przejść do zakończenia.

    Realizacja tego rozwiązania wymaga połączenia oczekiwania na komunikat (funkcja recv lub recvfrom) z funkcją select, która ma możliwość określenia czasu oczekiwania na deskryptorze pliku.

  4. Jeszcze inną możliwością jest napisanie serwera jako interakcyjnego, pracującego w pętli nieskończonej, ale pod kontrolą użytkownika. Poza oczekiwaniem na komunikaty podbić serwer mógłby nasłuchiwać na standardowym wejściu (deskryptorze 0) na możliwe polecenia użytkownika. Na żądanie użytkownika program każdorazowo wyświetlałby bieżący stan licytacji (liczbę otrzymanych podbić i aktualne oferty wszystkich przedmiotów). Użytkownik sam oceniłby kiedy licytacja się zakończyła (obserwując sumę podbić, lub brak nowych podbić), i mógłby wtedy wydać polecenie zakończenia pracy serwera.

    Na przykład, wpisanie 's' z klawiatury powinno spowodować wyświetlenie aktualnych ocen wszystkich produktów, natomiast wpisanie 'q' --- zakończenie pracy serwera.

    Aby to zrealizować, serwer musiałby oczekiwać na komunikaty klientów na gniazdku równocześnie z oczekiwaniem na polecenie użytkownika z klawiatury, i reagować na to, które pojawi się pierwsze. Można do tego wykorzystać funkcję select w sposób pokazany na wykładzie.

  5. Prawdopodobnie najlepszym rozwiązaniem jest połączenie powyższych opcji 3 i 4. Proces serwera oczekiwałby na polecenia użytkownika, ale jednocześnie sam śledziłby moment, kiedy komunikaty przestały napływać, i zawiadomiłby o tym użytkownika.

Praca na zajęciach - I termin

Minimalny wynik: uruchomiony program serwera poprawnie rejestruje swoje gniazdko odbierania komunikatów, i poprawnie odbiera komunikaty podbić, a program klienta poprawnie wysyła na to gniazdko wszystkie komunikaty podbić od wszystkich procesów agentów (2 punkty - na zajęciach).

Początkowo w celach testowych serwer może nawet wyświetlać na wyjściu fakt odebrania i treść każdego komunikatu. W tym celu można ustawić w programie jakąś minimalną liczbę podbić do wygenerowania (np. 10 na każdego agenta). Jednak po uruchomieniu komunikacji należy to zmienić i wysyłać większą liczbę komunikatów.

Pożądany wynik: program serwera odbiera wszystkie komunikaty wysłane przez klientów i wyświetla końcowe wyniki co najmniej za pomocą opisanego wyżej rozwiązania numer 1 (+1 punkt - na zajęciach).

Praca na zajęciach - II termin

Nie ma minimalnego wyniku w tym terminie. Ten termin jest premiowy.

Pożądany wynik: program realizuje któreś z rozwiązań problemu zakończenia licytacji i wyświetla wyniki końcowe (+1 punkt - za rozwiązanie numer 2 na zajęciach, albo +2 punkty - za rozwiązanie numer 3 lub numer 4 na zajęciach, albo +3 punkty - za rozwiązanie numer 5 na zajęciach, albo +1 punkt - za jedno z rozwiązań: 2, 3, 4 albo 5 w domu).

Raport

Raport z wykonania zadania, wysłany jednorazowo po jego zakończeniu całego zadania powinien zawierać krótkie podsumowanie uzyskanych wyników i/lub uwagi na temat ewentualnych problemów.

Proszę wyznaczyć maksymalną wartość parametru BIDDING_ROUNDS przy którym program wykonuje się w rozsądnym czasie (40-60 sekund na diablo, przy pozostałych parametrach ustawionych na domyślne wartości jak w zadaniu nr 6), i takie wyniki podać w raporcie.

Razem z raportem proszę przysłać w eportalu wersje źródłowe programów serwera i klienta możliwe do skompilowania i uruchomienia na diablo. Proszę zadbać, by użyć adresu gniazdka serwera, które będzie mógł uruchomić dowolny użytkownik. Sugerowane rozwiązanie adresu gniazdka serwera:

#define WZORZEC_ADRESU_SERWERA "/tmp/adres_serwera_uid_%d"

sprintf(serv_addrstr.sun_path, WZORZEC_ADRESU_SERWERA, getuid());
unlink(serv_addrstr.sun_path);

Powyższy sposób zapewnia (w rozsądnych granicach), że proces serwera danego użytkownika utworzy sobie gniazdko ze swoim indywidualnym adresem. Dobrą praktyką jest również usunięcie pliku adresowego (utworzonego gniazdka) przed zakończeniem programu serwera.

Ocena za oddany raport i program: 3 punkty.

Uwaga: w tym zadaniu w ocenie będę zwracał uwagę na uporządkowania programów. Jeśli w programie pozostaną ,,śmieci'' z wcześniejszych wersji zadania, niepotrzebne elementy, nieużywane zmienne, itp. to będę za taki bałagan odejmował punkty. Proszę również zadbać aby programy kompilowały się bez ostrzeżeń kompilatora.

Materiały

Dodatek specjalny - dla chętnych

Zamiast zastosowania komunikacji międzyprocesowej z wykorzystaniem gniazdek domeny UNIX z adresami wykorzystującymi ścieżkę (lub nazwę) wybranego pliku jest możliwe zaimplementowanie komunikacji sieciowej z wykorzystaniem gniazdek internetowych (domena INET). Wymaga to użycia internetowych struktur adresowych sockaddr_in. Serwer nie potrzebuje znać adresu IP komputera, natomiast musi znać numer portu. Może skorzystać z ustalonego numeru portu (ale zachodzi ryzyko, że może on być w użyciu przez inny program) albo może uzyskać dostępny numer portu od systemu. Klient powinien uzyskiwać adres IP i numer portu serwera z argumentów wiersza poleceń. W przypadku użycia numerycznego adresu IP wymagana jest tylko konwersja tego adresu na postać wewnętrzną, natomiast w przypadku użycia adresu symbolicznego (domenowego) konieczna jest jeszcze translacja adresu przez program klienta.

W przypadku zaimplementowania komunikacji internetowej pojawia się jeszcze jedna kwestia. Gdy komunikaty zawierają liczby zakodowane w postaci tekstowej (dziesiętnej), są one wysyłane jako strumień bajtów i ich transmisja przez sieć zawsze przebiega tak samo. Jednak w przypadku użycia kodowania binarnego należy wziąć pod uwagę możliwość, że klient i serwer mogą być uruchamiane na komputerach o innej architekturze procesora (BIG-ENDIAN/LITTLE-ENDIAN). Gdy ta architektura po obu stronach będzie inna, to wartości wielobajtowe (np. liczby int) byłyby przez te strony inaczej interpretowane. Przy komunikacji sieciowej stosuje się w tym celu funkcje konwersji wartości liczbowych na tzw. porządek sieciowy takie jak htonl/ntohl itp. Należy zatem zastosować taką konwersję.

Zauważmy, że komputery Sun Microsystems (diablo) mają architekturę BIG-ENDIAN, natomiast wszystkie PC-ty (panamint) mają architekturę LITTLE-ENDIAN.

Uwaga: sieciowa komunikacja bezpołączeniowa jest z definicji zawodna. Komunikaty mogą być zgubione i ani klient ani serwer nie będą mieli tego świadomości, ani nie nastąpi retransmisja. Dlatego problem zakończenia licytacji musi być rozwiązany metodą inną niż metoda numer 1. Również metoda numer 2 nie jest teoretycznie w 100% niezawodna, ponieważ specjalny komunikat o zakończeniu licytacji może zostać zgubiony. Metody 3,4,5 nadal będą poprawne, aczkolwiek należy przyjąć nieco dłuższe wartości opóźnień.