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:
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:
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:
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 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:
Web services
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
-
Testowanie
Aplikacje korzystające z sieci powinny dodatkowo zostać przetestowane pod kątem: