===== Code patterns ===== ==== Typowe podejścia do odbierania wiadomości ==== Metoda 1: (do użycia tylko jeśli rozmiar wiadomości jest wysyłany na początku lub jest z góry znany) * program dla każdego połączenia trzyma: * bufor * pozycję gdzie pisać do bufora * informację o rozmiarze wiadomości * program odbiera informację o wielkości wiadomości * dane są odbierane do zapełnienia bufora * po zapełnieniu wywoływana jest funkcja tworząca wiadomość z bufora Metoda 2: * przygotowuje się obiekt/funkcję parsującą strumieniowe dane, która: * przyjmuje porcję danych dowolnego rozmiaru * wywoływana jest wielokrotnie dla kolejnych porcji danych * analizuje dane które przyszły * pamięta w jakim stanie się znajduje * po przygotowaniu całych wiadomości zwraca je lub wywołuje funkcję do obsługi wiadomości * //przykład #1:// funkcja dodaje kolejną porcję do bufora, następnie dopóki w buforze znajdzie znak końca wiadomości usuwa gotową wiadomość z bufora \\ //przykład #2:// funkcja buduje na bieżąco strukturę danych reprezentującą przychodzący json \\ //przykład #3:// funkcja odbiera nazwy, rozmiar i zawartość kolejnych plików i zapisuje je na bieżąco na dysk * dane są odbierane w takich procjach jak przychodzą i są przekazywane powyższej funkcji ==== Produkowanie wiadomości z bufora ==== Zmieniając wiadomości (struktury / obiekty w języku programowania) w dane do wysłania w sieć (ciąg bajtów) (=serializacja) i na odwrót (=deserializacja), można: * ręcznie stworzyć odpowiednie funkcje * wykorzystać biblioteki / narzędzia automatycznie serializujące dane do: * postaci binarnej * standardowych formatów - json, xml Zwykle przygotowując ręcznie funkcję serializującą / deserializującą wiadomość: * na początku wiadomości umieszcza się informację o jej typie i wspólne dla wszystkich wiadomości pola * przy deserializacji po przeczytaniu typu wywołuje się metodę budującą konkretny rodzaj wiadomości * przy polach zmiennej długości albo na początku umieszcza się ich długość, albo na końcu ogranicznik ([[https://en.wikipedia.org/wiki/Delimiter|delimiter]]) * tworzy się testy jednostkowe które sprawdzają poprawność przygotowanych metod ==== Typowe podejścia do wysyłania wiadomości ==== Metoda 1: * program dla każdego połączenia trzyma listę wiadomości do wysłania (listę na którą składa się bufor, rozmiar i pozycja) * wysyłanie: - jeśli lista wiadomości do wysłania nie jest pusta: - dodaj na koniec listy - oceń czy połączenie nie zostało zerwane, jeśli tak sądzisz – obsłuż to - skończ - wyślij nieblokująco ile się da; jeśli wysłano wszystko, skończ - dodaj resztę wiadomości do listy wiadomości do wysłania - zażądaj czekania na zdarzenie zapisu * obsługa zdarzenia "można pisać": - wyślij nieblokująco wszystko co się da (pamiętaj o zwalnianiu pamięci) - jeśli wysłano wszystko – przestań czekać na zdarzenie zapisu * jeśli program potrzebuje pisać z wielu wątków, potrzeba jest synchronizacja (zamek) na obsługę zapisów Metoda 2: * dla każdego połączenia trzymany jest wątek do wysyłania, lista wiadomości, zamek i zmienna warunkowa * wątek przeznaczony na wysłanie danych w pętli wykonuje: - zajmuje zamek - dopóki lista jest pusta czeka na zmiennej warunkowej - bierze kolejną wiadomość z listy - zwalnia zamek - wysyła blokująco wiadomość * aby wysłać należy: - zająć zamek - wrzucić wiadomość na listę - ocenić czy połączenie nie zostało zerwane, jeśli tak – obsłużyć - wybudzić zmienną warunkową (warunkowo – jeśli na liście jest tylko świeżo dodana wiadomość) - zwolnić zamek Do oceny przy wysyłaniu czy połączenie zostało zerwane można sprawdzić: * czas od ostatniego udanego ''write()'' (trzeba go wcześniej zapisać) * ile wiadomości czeka na wysłanie * ile łącznie danych (w bajtach) czeka na wysłanie ==== Zadania ==== //Zadanie 1.// W plikach {{:sk2:ctr_srv.cpp|}} i {{:sk2:ctr_cli.cpp|}} znajduje się odpowiednio serwer i przykładowy klient usługi 'licznika'. * Przeczytaj kod metody ''main()'' w serwerze i przetestuj program netcatem * Przeczytaj kod metody ''main()'' i ''doWork()'' klienta. Jakiego stanu licznika spodziewałby się naiwny programista po przerwaniu programu klienta? * Serwer i klient nie radzą sobie ze sklejaniem się wiadomości czy odebraniem tylko części wiadomości w wywołaniu ''read()''. Popraw kod. ===== Good practices ===== ==== Podejście niskopoziomowe / wysokopoziomowe ==== Przy programach nie wymagających wysokiej wydajności zwykle szuka się rozwiązań wymagających jak najmniej czasu od programisty - czyli szuka się bibliotek które robią większość rzeczy za programistę. Dla osiągnięcia wysokiej wydajności zaleca się korzystanie z wydajnych, zwykle niskopoziomowych mechanizmów - albo bezpośrednio, albo za pomocą bibliotek nastawionych na wydajność (a nie wygodę użytkownika). Takie biblioteki są dostępne na różnym poziome abstrakcji, porównaj np. [[https://www.boost.org/doc/libs/release/libs/asio/|boost asio]] i [[https://en.wikipedia.org/wiki/Libevent|libevent]]). Wysokowydajne mechanizmy niskopoziomowe to: * Linux: epoll (omawiany na zajęciach) * *.BSD / MacOS: [[https://en.wikipedia.org/wiki/Kqueue|kqueue]] * MS Windows: [[https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports|IOCP]] ==== Web services ==== Wiele [[https://en.wikipedia.org/wiki/Web_service|usług sieciowych]] korzysta z ustandaryzowanych protokołów wymiany wiadomości, np. [[https://en.wikipedia.org/wiki/Representational_state_transfer|REST]] czy [[https://en.wikipedia.org/wiki/SOAP|SOAP]] ==== Middleware ==== Dostępnych też jest wiele //middleware// (warstw pośredniczących) które pozwalają programiście używać sieci bez pisania kodu obsługującego sieć, np: * [[https://en.wikipedia.org/wiki/Remote_procedure_call|RPC]] / [[https://en.wikipedia.org/wiki/Distributed_object_communication|RMI]] * [[https://pl.wikipedia.org/wiki/Message_Passing_Interface|MPI]] * [[https://en.wikipedia.org/wiki/Message_queue|Message Queue]] * [[https://en.wikipedia.org/wiki/Actor_model|model aktorów]] ==== Kryptografia, kompresja... ==== Często w komunikacji sieciowej korzysta się z dodatkowych warstw abstrakcji między TCP a wiadomościami aplikacyjnymi do osiągnięcia: * bezpieczeństwa (integralności, poufności, autentyczności) wykorzystując [[https://en.wikipedia.org/wiki/Transport_Layer_Security|SSL lub TLS]] * kompresji danych (o ile dane dają się kompresować) wykorzystując np. [[https://en.wikipedia.org/wiki/Zlib|deflate, zlib]], [[https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)|lz4]], [[https://en.wikipedia.org/wiki/Zstd|zstd]] ==== Testowanie ==== Aplikacje korzystające z sieci powinny dodatkowo zostać przetestowane pod kątem: * wytrzymałości na obciążenie - [[https://en.wikipedia.org/wiki/Load_testing|load testing]] * bezpieczeństwa i odporności na ataki