Dydaktyka:
FeedbackTo jest stara wersja strony!
Kolejne etapy "podróży" danych od karty sieciowej do aplikacji użytkownika:
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.
Więcej można doczytać np. tutaj: https://wiki.linuxfoundation.org/networking/kernel_flow
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ą 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).Funkcje wykonujące tylko operacje lokalne są nieblokujące.
socket, bind, listen,
setsockopt, gethostbyname / getaddrinfo (o ile funkcja nie
odpytywała serwera nazw, co można wymusić odpowiednimi flagami).shutdown – jest nieblokująca,
natomiast funkcja zamykająca gniazdo – close –
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.
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 EWOULDBLOCK1).
Gniazdo przestawia się w tryb nieblokujący zmieniając flagi (ustawienia)
deskryptora pliku (kod wymaga pliku nagłówkowego fcntl.h):
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
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:
recv(fileDescriptor, buff, buffSize, MSG_DONTWAIT);
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.
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 odbiorczym2).
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ń z tych laboratoriów można pobrać 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.
Aby ściągnąć i zbudować w katalogu z plikami źródłowymi3) wszystkie programy ręcznie, możesz wykonać komendy:
wget -O - http://www.cs.put.poznan.pl/jkonczak/_media/sk2:l3.tar.xz | tar xvJ cd l3 cmake . make
Programy serwera i klienta można uruchamiać na jednym komputerze, łącząc się z klienta na adres 127.0.0.1 lub localhost.
Zadanie 1 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 2 Przeczytaj kod klienta TCP który:
Połącz klienta do serwera z zadania 1.
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).
Zadanie 3 Przeczytaj kod klienta TCP który:
Połącz klienta do serwera z zadania 1.4)
Zadanie 4 Przeczytaj kod serwera TCP który po odebraniu połączenia, do jego zamknięcia, wykonuje w pętli:
Uruchom taki serwer na wybranym porcie.
Zadanie 5a Przeczytaj kod klienta TCP który wysyła cały alfabet po jednej literze.
Podłącz się (wielokrotnie) do serwera z zadania 4.
Zadanie 5b Przeczytaj kod klienta TCP który 20 razy wysyła kilkanaście znaków.
Podłącz się (wielokrotnie) do serwera z zadania 4.
Zadanie 6 Powtórz zadanie 4 dla UDP.
Zadanie 7 Powtórz zadanie 5a i 5b dla UDP.
Zadanie 8 Przeczytaj kod klienta TCP, który w wielokrotnie:
., wstawia b na jej początek, e na koniecNastę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'.
ip link set lo mtu 1500 tc qdisc add dev lo root netem delay 2ms 3ms 90% distribution pareto
Aby wrócić do domyślnych ustawień, wpisz:
ip link set lo mtu 65535 tc qdisc del dev lo root
Zadanie 9 Jak w strumieniu danych – czyli w połączeniu TCP – przesyłać dane tak by serwer wiedział kiedy cała wiadomość dotrze?
Zadanie 10 Jak w protokole UDP zapewnić, że cała wiadomość została odebrana?
Zadanie 11 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.
tc qdisc add dev lo root netem delay 5ms 5ms distribution normal loss 10%
Aby przywrócić domyślne zachowanie po wykonaniu zadania, wpisz:
tc qdisc del root dev lo
Zadanie 12 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 13 Wykonaj z roota poniższe polecenie, które spowoduje ograniczenie prędkości wysyłania pakietów i ponownie przetestuj program z poprzedniego zadania:
tc qdisc add dev lo root tbf rate 10kbps burst 1.5kb limit 10kb
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
Zadanie 14 Jak w protokole UDP radzić sobie z kolejnością pakietów? Jak radzić sobie ze zgubieniem pakietów?
connect, która rozpoczyna nawiązywanie połączenia w
tle, zwraca -1 i ustawia errno na EINPROGRESS.setsockopt(socket, SOL_SOCKET, SO_RCVBUF, …)