# Lindux Projekt wstępny [TOC] ## Temat i treść zadania Napisać wieloprocesowy system realizujący komunikację w języku komunikacyjnym Linda. Do komunikacji użyć potoków nazwanych (FIFO). Komunikacja koordynowana przez centralny proces. ## Skład zespołu * Damian Kolaska * Jakub Kowalczyk * Magdalena Majkowska * Alicja Turowska ## Data przekazania 29.04.2021 ## Interpretacja treści zadania Celem projektu jest umożliwienie komunikacji międzyprocesowej przy użyciu interfejsu wzorowanego na języku komunikacyjnym Linda. W ramach projektu powstanie biblioteka, której udogodnień będzie można użyć do stworzenia aplikacji serwera oraz klienta. Biblioteka będzie umożliwiała klientowi, po nawiązaniu połączenia z serwerem, przesyłanie krotek, ich odbieranie oraz podglądania krotek zgodnych z podanym przez klienta wzorcem. Elementem krotki może być liczba całkowita, zmiennopozycyjna, lub napis. We wzorcu klient może ograniczyć krotki do ciągu podanych typów elementów, bądź ograniczyć dany element krotki do konkretnych wartości za pomocą operatorów porównania (=, <, >). Klient po zakończeniu korzystania z systemu komunikacji powinien zamknąć połączenie. System zostanie zrealizowany za pomocą potoków nazwanych z procesem centralnym. Proces centralny będzie jednostką odpowiedzialną za przechowywanie krotek i udostępnianie powyższych funkcjonalności innym procesom (procesom klienckim), z którymi komunikacja będzie zapewniona przez zestaw potoków nazwanych stworzonych przez proces centralny do obsługi konkretnego klienta oraz potokom pełniącym rolę magistrali, służącym do nawiązania połączenia. ### Diagram architektury ![](https://i.imgur.com/lrbfUkK.png) ## Krótki opis funkcjonalny ### Sekwencja zdarzeń #### Start systemu 1. Serwer zostaje uruchomiony. 2. Zostają utworzone dwa potoki, które będą pełniły rolę głównej magistrali. #### Nawiązanie połączenia 1. Klient wysyła na głównej magistrali prośbę o otwarcie połączenia. 2. Serwer odbiera komunikat od klienta i tworzy zestaw potoków do komunikacji z danym klientem. W odpowiedzi wysyła nazwę zestawu potoków. 3. Serwer, na potrzeby obsługi klienta, tworzy nowy wątek, który w pętli, przy użyciu funkcji *select()* oczekuje i wykonuje żądania danego klienta. #### Wysłanie krotki 1. Klient wysyła krotkę. 2. Wątek serwera odbiera krotkę i sięga do przestrzeni krotek. 3. Aby zapewnić spójność, operacje na krotkach będą wykonywane w sekcji krytycznej. Synchronizacja zostanie zapewniona za pomocą mutexu. #### Odbieranie krotki 1. Klient wysyła do serwera komunikat o chęci pobrania danej krotki 2. Na podstawie przekazanego w komunikacie wzorca krotki, serwer odszukuje odpowiadający mu zestaw danych. Jeżeli odczyt był operacją input, następuje zablokowanie sekcji krytycznej. 3. Jeżeli krotka została odnaleziona, potokiem zwracana jest jej zawartość. 3.1 W przeciwnym wypadku odblokowujemy sekcję krytyczną i czekamy na pojawienie się interesującej na krotki. Aby nie doprowadzić do zagłodzenia oczekujących procesów, zostanie zdefiniowana kolejka FIFO, która pilnować będzie tego, aby proces który wcześniej wysłały komunikat do serwera, miały większy priorytet. ## API Całość znajduję się w przestrzeni nazw *linda* ```cpp using TupleElem = std::variant<int, double, std::string>; enum class Operator { None, All, GreaterThan, LessThan, ... } class Pattern { public: TupleElem value; Operator op; bool matches(TupleElem& elem) { if (value.index() != elem.index()) return false; if (op == All) return true; if (op == None) {/* sprawdz wartość */ } ... return true; } }; // klasy zdefiniowane dla poprawy interfejsu Pattern("hello") -> String("hello") class String : public Pattern { public: // operator parsujemy w konstruktorze String(std::string pattern) { if (pattern[0] == '*') op = Operator::All; ... } } class Float : public Pattern {...} class Integer : public Pattern {...} class Server { public: void host(); // inicjuje serwer void close(); void accept(); // akceptuje połączenie void listen(); // nasłuchuje komunikatów (nowe połączenia, krotki) }; class Client { public: void connect(uuid client_id); void disconnect(); std::vector<TupleElem> input(std::vector<Pattern> tuple, int timeout); void output(std::vector<TupleElem> tuple); std::vector<TupleElem> read(std::vector<Pattern> tuple, int timeout); }; ``` ### Przykładowe użycie Serwer ```cpp linda::Server server; server.host(); while (...) { ... } server.close(); ``` Klient 1 ```cpp linda::Client client1; client1.connect("client1"); client1.output({ 1, "hello", 2.5, "abc" }); ... client1.disconnect(); ``` Klient 2 ```cpp linda::Client client2; client2.connect("client2"); client2.input({Integer("1"), String("*"), Float("*"), String("abc")) }, 1000); ... client1.disconnect(); ``` ## Struktury danych ### Przechowywania krotek przez serwer Krotki będą przechowywane w kontenerze asocjacyjnym (np. std::unordered_map) gdzie kluczem będzie rozmiar krotki. Pozwala to na szybką identyfikacje krotek o danej długości. ```cpp std::unordered_map<int, std::vector<TupleElem>> tuples; ``` ### Rejestr połączeń Serwer będzie przechowywał rejestr połączeń. Kluczem jest uuid klienta. Wartość to nazwa utworzonego potoku. ```cpp std::unordered_map<uuid, std::string> connections; ``` ### Reprezentacja krotek i wzorców krotek Krotki będą reprezentowane jako tablica (std::vector) obiektów TupleElem. ```cpp std::vector<TupleElem> tuple = {1, 2.5, "hello"}; ``` Szablony krotek będą reprezentowane przez tablicę (std::vector) obiektów pochodnych klasy Pattern (String, Float, Integer) ```cpp std::vector<Pattern> pattern = {Integer("*"), Float(">2.5"), String("hello")}; ``` ## Synchronizacja Serwer oparty będzie o wielowątkowość. Użyjemy implementacji wątków oferowanej przez bibliotekę pthreads. Wątki będą korzystać ze wspólnego bufora przechowującego krotki, synchronizowanego mutexem. Również główna magistral zostanie objęta mechanizmem synchronizacji, aby różni klienci nie przeszkadzali sobie w nawiązywaniu połączenia z serwerem. ## Struktura komunikatów przesyłanych w potoku ```cpp #define CONNECT 0b10000000 #define SEND_TUPLE 0b01000000 #define LINDA_READ 0b00100000 #define LINDA_INPUT 0b00010000 #define LINDA_OUTPUT 0b00001000 #define TYPE_INT 0b00000100 #define TYPE_FLOAT 0b00000010 #define TYPE_STRING 0b00000001 struct __attribute__((__packed__)) LindaMessage { uint8_t control_mask; int tuple_integer; double tuple_float; char tuple_string[100]; }; ``` **Logika po stronie serwera** ```cpp if (msg.control_mask & SEND_TUPLE) { if (msg.control_mask & LINDA_READ) { if (msg.control_mask & TYPE_INT) { int value = msg.tuple_integer; } else if (msg.control_mask & TYPE_FLOAT) ... else if (msg.control_mask & TYPE_STRING) ... } else if (msg.control_mask & LINDA_INPUT) { ... } else if (msg.control_mask & LINDA_OUTPUT) { ... } } else if (msg.control_mask & CONNECT) { /* * Klient chce się połączyć. Jeśli udało się połączyć serwer odsyła CONNECT * oraz nazwę kolejki w tuple_string. Jeśli nie, wysyła !CONNECT. */ } else if (!(msg.control_mask & CONNECT)) { /* * Klient chce się rozłączyć. Serwer usuwa potoki. */ } ``` ## Zobrazowanie pełnej sekwencji zdarzeń ![](https://i.imgur.com/lJwSm96.png) ## Moduły * tuple.cpp - definicja klas związanych z obsługą krotek * serializer.cpp - serializacja i deserializacja krotek * client.cpp - definicja klienta * server.cpp - definicja serwera ## Testy ### Testy jednostkowe #### Przesłanie struktury przez potok * za pomocą serializatora tworzymy strukturę X * do potoku podajemy strukturę X * po odczytaniu danych z potoku powinniśmy być w stanie odtworzyć strukturę X #### Otwarcie połączenia * stworzenie procesów serwera i klienta * klient nawiązuje połączenie podając uuid X * serwer akceptuje połączenie * ponowna próba połączenia się z uuid X powinna zostać odrzucona #### Operacje na krotkach read() * krotka X w przestrzeni * wywołanie operacji read na serwerze * serwer powinien zwrócić krotkę * przestrzeń powinna wciąż zawierać krotkę X input() * krotka X w przestrzeni * wywołanie operacji input na serwerze * serwer powinien zwrócić krotkę * przestrzeń powinna być pusta output() * pusta przestrzeń * wywołanie operacji output na serwerze * przestrzeń powinna zawierać krotkę X #### Testowanie wzorców * tworzymy element tupli X * tworzymy wzorzec tupli Y * sprawdzamy czy element X pasuje do wzorca Y ### Testy integracyjne * Serwer, jeden klient, nawiązanie połączenia, przesłanie krotki, zakończenie połączenia ### Testy akceptacyjne * Test pełnego systemu - przesyłanie i odbieranie krotek przez wielu klientów ## Stos technologiczny ### Język Rozwiązanie planujemy zaimplementować w języku C++17. ### Biblioteki * Loguru https://github.com/emilk/loguru - logger * Google Test - biblioteka do testowania * pthread - implementacja wielowątkowości ### Środowisko developerskie * CLion https://www.jetbrains.com/clion/ - IDE (edytor kodu, debugger, profiler(perf), ...) * Visual Studio Code - edytor kodu * gdb z dodatkiem https://github.com/pwndbg/pwndbg - debugger * ClangFormat - formater kodu * git - system kontroli wersji * ASAN https://clang.llvm.org/docs/AddressSanitizer.html - address sanitizer