przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
bio-psiec
»
sockets_caveats
bio-psiec:sockets_caveats
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
====== Dopuszczalne zachowanie się funkcji sieciowych ====== ===== Bufory w komunikacji sieciowej ===== Kolejne etapy "podróży" danych od karty sieciowej do aplikacji użytkownika: <html><div style="margin-top:-1.4em"></html> - pakiet odbierany przez kartę sieciową (NIC) jest najpierw zapisywany do wewnętrznego bufora NIC, - NIC kopiuje dane ze swoich buforów (używając DMA) do pamięci głównej (RAM) i wywołuje przerwanie, - system operacyjny (OS) w procedurze obsługi przerwania przetwarza kolejno każdy z odczytanych pakietów; dla pakietów które przenoszą dane już istniejącego połączenia, \\ OS kopiuje dane do powiązanego z gniazdem systemowego bufora odbiorczego, - kiedy program zażądał od systemu operacyjnego odbioru danych z sieci i są dane w buforze systemowym, OS kopiuje dane z systemowego bufora odbiorczego do bufora podanego przez użytkownika. <html><div style="margin-top:-1.4em"></html> Zauważ że te kroki mogą dziać się niezależnie od siebie, a funkcja do odbioru danych z sieci tak naprawdę odczytuje dane z powiązanego z gniazdem bufora w systemie operacyjnym. W podobny sposób dane są buforowane przy wysyłaniu – kiedy program odpowiednią funkcją żąda wysłania danych przez sieć, podany przez użytkownika bufor z danymi jest kopiowany do powiązanego z gniazdem bufora systemowego. Stamtąd, niekoniecznie w trakcie działania wspominanej funkcji, dane są przenoszone do powiązanej z kartą kolejki danych do wysłania, skąd ostatecznie trafiają do kolejki na karcie sieciowej. <small>Więcej można doczytać np. tutaj: https://wiki.linuxfoundation.org/networking/kernel_flow</small> ===== Blokowanie i wartości zwracane przez funkcje odbierające / wysyłające dane ===== ==== Funkcje blokujące / nieblokujące ===== Zasadniczo funkcje które wymagają wysłania lub odebrania danych z sieci mogą się //zablokować//, tj. po ich wywołaniu program zatrzymuje się do czasu zakończenia żądanej operacji (por. z definicją [[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_48|blocking]] z POSIX). Przykłady funkcji które mogą się zablokować to: * ''read'' / ''recv'' / ''recvfrom'' (czeka aż przyjdą dane) * ''write'' / ''send'' (czeka aż zwolni się miejsce w buforze nadawczym) * ''connect'' (czeka aż uda się nawiązać połączenie) * ''accept'' (czeka aż przyjdzie nowe połączenie) * ''gethostbyname'' / ''getaddrinfo'' (czeka na odpowiedź od serwera nazw, o ile zaszła konieczność odpytania). <html><div style="margin-top:-1.4em"></div></html> Funkcje wykonujące tylko operacje lokalne są nieblokujące. \\ Przykłady takich funkcji (sieciowych) to: ''socket'', ''bind'', ''listen'', ''setsockopt'', ''gethostbyname'' / ''getaddrinfo'' (o ile funkcja nie odpytywała serwera nazw, co można wymusić odpowiednimi flagami).\\ <small>Funkcja zamykająca połączenie – ''shutdown'' – jest nieblokująca, natomiast funkcja zamykająca gniazdo – ''close'' – [[https://pubs.opengroup.org/onlinepubs/9799919799/functions/close.html|może się blokować]] jeżeli dla gniazda połączeniowego ustawiono opcję SO_LINGER, nakazującą przed zamknięciem połączenia wysłać zbuforowane w systemie danych, ale nie dłużej niż na czas podany w opcji SO_LINGER. </small> <html><style> div.wrap_shortcodeexample{display:inline-block;margin-top:-1.4em} div.wrap_shortcodeexample pre{padding-top:.3em;padding-bottom:.3em} div.wrap_shortcodeexample_nomargin{display:inline-block;margin-top:-1.4em} div.wrap_shortcodeexample_nomargin pre{margin:0em;padding-top:.3em;padding-bottom:.3em} div.wrap_shortcodeexample_nomargin + p{margin-top:0em} </style></html> Domyśle blokujące zachowanie można zmienić przestawiając gniazdo w //tryb nieblokujący//. \\ **Jeśli wykonanie żądanej funkcji na gnieździe w trybie nieblokującym nie jest możliwe bez czekania, to wykonanie funkcji nie powodzi się (zwracany jest wynik ''-1'') a zmienna ''errno'' jest ustawiana na ''EAGAIN'' lub ''EWOULDBLOCK''**(( Wyjątkiem jest funkcja ''connect'', która rozpoczyna nawiązywanie połączenia w tle, zwraca ''-1'' i ustawia ''errno'' na ''EINPROGRESS''.)). Gniazdo przestawia się w tryb nieblokujący zmieniając flagi (ustawienia) deskryptora pliku <small>(kod wymaga pliku nagłówkowego ''fcntl.h'')</small>: <WRAP shortcodeexample_nomargin> <code cpp>fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);</code> </WRAP> Niektóre funkcje sieciowe (np. ''recv'', ''send'') pozwalają na ustawienie w polu flag wartości ''MSG_DONTWAIT'', która wykonuje żądaną operację w trybie nieblokującym niezależnie od tego w jakim trybie pracuje gniazdo: <WRAP shortcodeexample> <code cpp>recv(fileDescriptor, buff, buffSize, MSG_DONTWAIT);</code> </WRAP> **Uwaga: w BSD/POSIX socket API każde nowo utworzone gniazdo działa w trybie blokującym, nawet jeżeli gniazdo zostało utworzone w wyniku działania funkcji ''accept'' na nasłuchującym gnieździe działającym w trybie nieblokującym.** ==== Read i Write – ilość odczytanych / zapisanych bajtów ==== Funkcje ''read'', ''recv'',… często odczytują mniej bajtów niż podana w argumentach wielkość bufora. Powód? Mniej danych przyszło lub mniej mieści się w systemowym buforze odbiorczym((Rozmiar bufora odbiorczego można ustawić funkcją: ''setsockopt(socket, SOL_SOCKET, SO_RCVBUF, …)'')). Funkcja ''recv'' i podobne ma pole na flagi; można tam ustawić flagę ''MSG_WAITALL'' która żąda odebrania dokładnie tylu bajtów ile podano. Mimo to ''recv'' może odebrać mniej bajtów, np. w przypadku zakończenia połączenia. Normalnie funkcje zapisujące blokują się do momentu aż całe dane zostaną wpisane do systemowego bufora nadawczego dla gniazda (być może dane będą tam umieszczane porcjami, np. jeśli nakazano wysłać więcej danych niż rozmiar bufora). Jeśli gniazdo pracuje w trybie nieblokującym, to funkcje takie jak ''write'', ''send'', … zapiszą ("wyślą") tylko część danych jeśli w buforze systemowym skończy się miejsce. Dlatego **zarówno przy odbieraniu jak i nieblokującym wysyłaniu trzeba sprawdzać zwracaną ilość przetworzonych danych**. ===== Kody źródłowe do zadań ===== Kody źródłowe do zadań z tych laboratoriów można pobrać **{{:sk2:l3.tar.xz|stąd}}**. Zadania należy wykonywać na Linuksie – do części z nich potrzeba zmienić ustawienia sieci na takie które symulują opóźnienia sieci lub gubienie pakietów. <small> Aby ściągnąć i zbudować w katalogu z plikami źródłowymi((Co jest złą praktyką – kod należy budować w osobnym katalogu. Zainteresowanych odsyłam [[https://johnfarrier.com/in-source-vs-out-of-source-builds/|tutaj]])) wszystkie programy ręcznie, możesz wykonać komendy: <WRAP shortcodeexample> <code> wget -O - http://www.cs.put.poznan.pl/jkonczak/_media/sk2:l3.tar.xz | tar xvJ cd l3 cmake . make </code> </WRAP> </small> Programy serwera i klienta można uruchamiać na jednym komputerze, łącząc się z klienta na adres 127.0.0.1 lub //localhost//. ===== Zadania ===== ~~Zadanie.#~~ Przeczytaj kod serwera TCP, który w pętli akceptuje nowe połączenia i je ignoruje (tzn. nie odbiera ani nie wysyła danych, nie zamyka połączeń). \\ Uruchom taki serwer na wybranym porcie. ~~Zadanie.#~~ Przeczytaj kod klienta TCP który: * łączy się pod podany adres * w pętli: * wysyła porcję danych * wypisuje ile łącznie wysłał bajtów <html><div style="margin-top:-1.4em"></div></html> Połącz klienta do serwera z zadania 1. <small> Możesz sprawdzić poleceniem ''netstat''/''ss'', np. ''netstat -tnp''/''ss -tnp'', ile bajtów jest w buforze odbiorczym/nadawczym (kolumny Recv-Q i Send-Q). </small> ~~Zadanie.#~~ Przeczytaj kod klienta TCP który: * łączy się pod podany adres * ustawia tryb nieblokujący * w pętli: * wysyła porcję danych * jeśli wysłał całą porcję danych, wypisuje ile łącznie wysłał bajtów * jeśli wysłał mniej danych niż chciał, wypisuje stosowny komunikat i kończy program <html><div style="margin-top:-1.4em"></div></html> Połącz klienta do serwera z zadania 1.((W tym zadaniu warto porównać wynik dla klienta z lokalnego komputera i ze zdalnego komputera)) ===== Protokół strumieniowy / zorientowany na wiadomość ===== ~~Zadanie.#~~ Przeczytaj kod serwera TCP który po odebraniu połączenia, do jego zamknięcia, wykonuje w pętli: * odbiera do 10 bajtów * wypisuje ile bajtów odebrał i wypisuje odebrane dane <html><div style="margin-top:-1.4em"></div></html> Uruchom taki serwer na wybranym porcie. ~~Zadanie.#~~a Przeczytaj kod klienta TCP który wysyła cały alfabet po jednej literze. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ ~~Zadanie.#5~~b Przeczytaj kod klienta TCP który 20 razy wysyła kilkanaście znaków. Podłącz się (wielokrotnie) do serwera z zadania 4. ~~Zadanie.#~~ Powtórz zadanie 4 dla UDP. \\ ~~Zadanie.#~~ Powtórz zadanie 5a i 5b dla UDP. <small> ~~Zadanie.#~~ Przeczytaj kod klienta TCP, który w wielokrotnie: <html><div style="margin-top:-1.4em"></div></html> * losuje wielkość tablicy znaków do wysłania (w zakresie 1500 do 3500) * wypełnia tablicę znakiem ''.'', wstawia ''b'' na jej początek, ''e'' na koniec * wysyła całą tablicę * czeka 10ms <html><div style="margin-top:-1.4em"></div></html> Następnie przeczytaj kod serwera TCP który po odebraniu połączenia, do jego zamknięcia, w pętli odbiera do 1MB danych, a następnie sprawdza w każdej odebranej porcji na jakich pozycjach znajdują się znaki 'b' i 'e'. \\ Uruchom program serwera i połącz się do niego klientem. Jeżeli uruchamiasz serwer i klienta na tym samym komputerze, to przed uruchomieniem klienta zmień ustawienia swojego interfejsu sieciowego poleceniami: <WRAP shortcodeexample_nomargin> ip link set lo mtu 1500 tc qdisc add dev lo root netem delay 2ms 3ms 90% distribution pareto </WRAP> Aby wrócić do domyślnych ustawień, wpisz: <WRAP shortcodeexample> ip link set lo mtu 65535 tc qdisc del dev lo root </WRAP> </small> ~~Zadanie.#~~ Jak w strumieniu danych – czyli w połączeniu TCP – przesyłać dane tak by serwer wiedział kiedy cała wiadomość dotrze? ~~Zadanie.#~~ Jak w protokole UDP zapewnić, że cała wiadomość została odebrana? ===== Kolejność danych i (nie)zawodność ===== ~~Zadanie.#~~ Wykonaj z roota poniższe polecenie, które spowoduje pomieszanie kolejności pakietów wysłanych przez interfejs ''lo''. \\ Następnie uruchom ponownie programy z zadań 4÷7. <html><div style="margin-top:-1.4em"></html> <code> tc qdisc add dev lo root netem delay 5ms 5ms distribution normal loss 10% </code> <html></div></html> Aby przywrócić domyślne zachowanie po wykonaniu zadania, wpisz: <html><div style="margin-top:-1.4em"></html> <code> tc qdisc del root dev lo </code> <html></div></html> ~~Zadanie.#~~ Przeczytaj kod programu, który wyśle 1000 pakietów UDP zawierających kolejne liczby (z 10µs przerwą przed wysłaniem każdego pakietu). \\ Połącz tego klienta z serwerem z zadania 4. ~~Zadanie.#~~ Wykonaj z roota poniższe polecenie, które spowoduje ograniczenie prędkości wysyłania pakietów i ponownie przetestuj program z poprzedniego zadania: <html><div style="margin-top:-1.4em"></html> <code> tc qdisc add dev lo root tbf rate 10kbps burst 1.5kb limit 10kb </code> <html></div></html> <small> Dla TCP możesz zobaczyć gromadzone przez system operacyjny informacje używane do dobrania prędkości do możliwości łącza używając polecenia ''ss'' z przełącznikiem ''i'', np: ''ss -atip'' </small> ~~Zadanie.#~~ Jak w protokole UDP radzić sobie z kolejnością pakietów? Jak radzić sobie ze zgubieniem pakietów?
bio-psiec/sockets_caveats.1764204138.txt.gz
· ostatnio zmienione: 2025/11/27 01:42 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry