Dokumentacja zawierająca opis organizacji sieci, przesyłanych komunikatów i protokołu komunikacji węzeł-węzeł tutaj, lub w wersji pdf. Zostało zaimplementowane i przetestowane wszystko co jest zawarte poleceniu.
Do uruchamiania testów (./tests/*.sh), warto jest używać tsp (instalacja na
ubuntu: sudo apt install task-spooler).
Poniżej sposób kompilacji i uruchomienia przykładowego testu, tak aby wygodnie było wyświetlać logi z procesów
uruchomionych w tle:
tsp -K # terminacja serwera tsp aby numeracja tasków była od 0
tsp -S 50 # maksymalnie 50 zadań równolegle -- należy zwiększyć w przypadku dodania testów uruchamiających więcej zadań w tle
echo ""
echo "============================================================== compile"
echo ""
javac my_server/NodeConnectionHandler.java my_server/DatabaseNode.java
javac my_server/DatabaseClient.java
echo ""
echo "============================================================== running tests: "
echo ""
# tutaj można odkomentować test który chcemy uruchomić
# bash tests/script-1-0.sh
# bash tests/script-1-1_1.sh
# bash tests/script-1-1_2.sh
# bash tests/script-1-1_3.sh
# bash tests/script-1-1_4.sh
# bash tests/script-2-0_1.sh
# bash tests/script-2-1_1.sh
# bash tests/script-2-1_2.sh
# bash tests/script-2-1_2.sh
# bash tests/script-3-0_1.sh
# bash tests/script-3-0_1.sh
# bash tests/script-3-1_1.sh
bash tests/script-7-1.sh
# bash tests/script-7-2.sh
# bash tests/script-7-p.sh
sleep 1
echo ""
echo "=============================================================="
echo ""
tsp # wyświetlenie listy procesów uruchomionych w tle
echo ""
echo "============================================================== Output 1:"
echo ""
tsp -c 0 # wyświetlenie stdout+stderr dla pierwszego procesu uruchomionego w tle w danym teście
# ...Zaimplementowane są 3 klasy:
- Klasa klienta
DatabaseClient-- załączona do treści zadania - Klasa klienta
DatabaseNode-- realizująca treść zadania (węzeł uproszczonej rosproszonej bazy danych). - Pomocnicza (dla
DatabaseNode) klasaNodeConnectionHandlerrealizuje wysyłanie poleceń innym węzłom -- jej rola sprowadza się przede wszystkim do nawiązania połączenia z innym węzłem, nadaniem komunikatu i odebraniu odpowiedzi.
W pętli głównej węzła (w metodzie DatabaseNode.start), socket serwerowy nasłuchuje na nowe połączenia od zarówno
klientów jak i innych węzłów.
Aby w trakcie obsługi komunikatu mógł dalej przyjmować polecenia (np. ponowny komunikat dotyczący tego samego zapytania
tego samego klienta -- z tym samym TASK_ID tylko po
to żeby zwrócić "ERROR"),
komunikaty srv__get-value , srv__set-value, srv__find-key, srv__get-min, srv__get-max, oraz odpowiadające im
komunikaty klienta, są realizowane asynchronicznie (na nowym tymczasowym wątku).
Opis realizowanych komunikatów węzeł-węzeł jest w dokumentacji. W przypadku każdego komunikatu, protokół sprowadza się do jednorazowego wysłania polecenia i odebraniu odpowiedzi/wyniku/informacji o niepowodzeniu.
Implementacja komunikatów srv__get-value, srv__set-value, srv__find-key, srv__get-min, srv__get-max jest
bardzo podobna.
Wszystkie wymagają iteracji po wszystkich połączeniach z innymi węzłami aż do znalezienia i ewentualnie podmiany
wartości,
lub zastosowanie min lub max na odebranych wynikach (i własnej przetrzymywanej parze klucz-wartość).
Każdy z tych komunikatów dostaje unikalne TASK_ID, nadawane przez węzeł który bespośrednio otrzymał odpowiedni
komunikat od klienta (DatabaseClient).
Wysyłanie wszystkich komunikatów węzeł-węzeł (srv__*) jest owiniętę pomocniczą klasą NodeConnectionHandler dla
przejżystości implementacji.
- Konstruktor
DatabaseNode-- przyjmuje sparsowane argumenty z polecenia niezbędne do utworzenia węzła. Konstruje połączenia z innymi węzłami, wysyłającsrv__connectdo każdego z nich. DatabaseNode.start-- zawiera pętlę główną używającąServerSocket.accept. Pętla jest przerywana w wyniku otrzymania komunikatu"terminate"od klienta (po wcześniejszym wysłaniu komunikatusrv__disconnectdo połączonych bezpośrednio węzłów).DatabaseNode.handleNewSocket-- implementuje obsługę nowego obiektuSocketotrzymaną w metodziestart, odczytuje i parsuje komunikat, odsyła wynik komunikatu.DatabaseNode.handleNewRequest-- realizuje poszczególne polecenia -- przyjmuje komunikat i jego argumenty, i zwraca odpowiedź/wynik/informację o niepowodzeniu. Niektóre komunikaty realizuje asynchronicznie na oddzielnym tymczasowym wątku -- dla przejżystości realizacje są owinięte metodąhandleAsyncRequest.DatabaseNode.getNewTaskId-- tworzy nowy globalnie unikalny napis"TASK_ID:<licznik_węzła>:<adres_węzła>:<tcpport_węzła>, oraz inkrementuje licznik zadań węzła (inicjowany zerem w momencie skonstruowaniaDatabaseNode).