Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:good_practices

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 (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:
    1. jeśli lista wiadomości do wysłania nie jest pusta:
      1. dodaj na koniec listy
      2. oceń czy połączenie nie zostało zerwane, jeśli tak sądzisz – obsłuż to
      3. skończ
    2. wyślij nieblokująco ile się da; jeśli wysłano wszystko, skończ
    3. dodaj resztę wiadomości do listy wiadomości do wysłania
    4. zażądaj czekania na zdarzenie zapisu
  • obsługa zdarzenia "można pisać":
    1. wyślij nieblokująco wszystko co się da (pamiętaj o zwalnianiu pamięci)
    2. 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:
    1. zajmuje zamek
    2. dopóki lista jest pusta czeka na zmiennej warunkowej
    3. bierze kolejną wiadomość z listy
    4. zwalnia zamek
    5. wysyła blokująco wiadomość
  • aby wysłać należy:
    1. zająć zamek
    2. wrzucić wiadomość na listę
    3. ocenić czy połączenie nie zostało zerwane, jeśli tak – obsłużyć
    4. wybudzić zmienną warunkową (warunkowo – jeśli na liście jest tylko świeżo dodana wiadomość)
    5. 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 ctr_srv.cpp i 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. boost asio i libevent). Wysokowydajne mechanizmy niskopoziomowe to:

  • Linux: epoll (omawiany na zajęciach)
  • *.BSD / MacOS: kqueue
  • MS Windows: IOCP

Web services

Wiele usług sieciowych korzysta z ustandaryzowanych protokołów wymiany wiadomości, np. REST czy 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:

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 SSL lub TLS
  • kompresji danych (o ile dane dają się kompresować) wykorzystując np. deflate, zlib, lz4, zstd

Testowanie

Aplikacje korzystające z sieci powinny dodatkowo zostać przetestowane pod kątem:

  • wytrzymałości na obciążenie - load testing
  • bezpieczeństwa i odporności na ataki
sk2/good_practices.txt · ostatnio zmienione: 2022/11/21 21:52 przez jkonczak