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
»
sk2
»
sockets_caveats
sk2:sockets_caveats
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
====== Interface gniazd BSD (2/2) ====== ===== Kody źródłowe do zadań ===== Z doświadczeń lat ubiegłych wynika, że sprawne pisanie prostych programów stanowi problem dla części uczestników zajęć. Stąd w trakcie tych zajęć proszę korzystać z gotowych kodów źródłowych, analizując je przez uruchomieniem: **{{:sk2:l3.tar.xz|Kody źródłowe zadań}}** <small> Aby ściągnąć i zbudować programy ręcznie, możesz wykonać: <html><div style="margin-top:-1.2em"></html> <code> wget -O - http://www.cs.put.poznan.pl/jkonczak/_media/sk2:l3.tar.xz | tar xvJ cd l3 mkdir build cd build cmake .. make </code> </small> Programy serwera i klienta można uruchamiać na jednym komputerze, łącząc się z klienta na adres 127.0.0.1 lub //localhost//. ===== 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 do już istniejącego połączenia, \\ OS kopiuje dane do powiązanego z gniazdem systemowego bufora odbiorczego, - kiedy program zażądał od systemu operacyjnego odczyt danych i są dane w buforze systemowym: \\ OS kopiuje dane z systemowego bufora odbiorczego do bufora podanego przez użytkownika. W podobny sposób dane są buforowane przy wysyłaniu – podany przez użytkownika bufor kopiowany jest do powiązanego z gniazdem bufora systemowego, z którego dane są przenoszone do powiązanej z kartą kolejki danych do wysłania, skąd trafiają do kolejki na karcie. <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 ===== ==== Read i Write - ilość odczytanych / zapisanych bajtów ==== Funkcje ''read'', ''recv'',… często zwracają mniej niż podana w argumentach wielkość bufora. Powód? Mniej danych przyszło lub skończyło się miejsce w buforze odbiorczym((Rozmiar bufora odbiorczego można ustawić funkcją: ''setsockopt(socket, SOL_SOCKET, SO_RCVBUF, …)'')). Może się tak stać nawet pomimo ustawienia flagi ''MSG_WAITALL'' w funkcji ''recv'' i podobnych (żądającej odebrania dokładnie tylu bajtów ile podano), np. w przypadku zakończenia połączenia. Funkcje ''write'', ''send'',… mogą też w pewnych okolicznościach wysłać mniejszą ilość bajtów niż zażądano. Jest to spowodowane zapełnieniem systemowego buforu wysyłania (czyli jeśli dane są dostarczane do wysłania szybciej niż można je wysyłać). Normalnie funkcje zapisujące blokują się do momentu aż w buforze będzie dość miejsca. Jeśli jednak gniazdo pracuje w trybie nieblokującym (patrz niżej), to funkcje takie jak ''write'', ''send'', … zapiszą tylko część danych jeśli w buforze systemowym skończy się miejsce. Dlatego zarówno przy odbieraniu jak i wysyłaniu trzeba sprawdzać zwracaną ilość przetworzonych danych. ==== 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'' – może się blokować dla gniazd połączeniowych dla których ustawiono na nim opcję SO_LINGER na czas wysłania danych z bufora systemowego, ale nie dłużej niż ilość sekund podana w opcji SO_LINGER[[https://pubs.opengroup.org/onlinepubs/9799919799/functions/close.html|[1]]].</small> Można zmienić domyśle blokujące zachowanie 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 podobnie jak każdy inny deskryptor pliku: <html><div style="margin:-1.2em 0 -1.2em 0"></html> <code cpp>fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)</code> <html></div></html> 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: <html><div style="margin-top:-1.2em"></html> <code cpp>recv(fileDescriptor, buff, buffSize, MSG_DONTWAIT);</code> <html></div></html> **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.** <small> Według standardu POSIX z 2024 roku tryb nieblokujący można ustawiać też sumując ostatni argument funkcji ''socket'' ze stałą ''SOCK_NONBLOCK'' [[https://pubs.opengroup.org/onlinepubs/9799919799/functions/socket.html|[1]]], jak i ustawiając flagę ''SOCK_NONBLOCK'' w polu flag funkcji ''accept4'' [[https://pubs.opengroup.org/onlinepubs/9799919799/functions/accept.html|[2]]] (wersja funkcji ''accept'' z dodatkowym polem flag). Linux od dawna wspiera stałą ''SOCK_NONBLOCK''. </small> ==== Zadania ===== ~~Zadanie.#~~ Stwórz serwer 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ń). ~~Zadanie.#~~ Stwórz klienta TCP który: * łączy się pod podany adres * w pętli: * wysyła dane * wypisuje kolejny numer i ilość wysłanych danych <html><div style="margin-top:-1.4em"></div></html> Połącz klienta do serwera z zadania 1. <small> Możesz sprawdzić poleceniem ''netstat'', np. ''netstat -tn'', ile bajtów jest w buforze odbiorczym/nadawczym (kolumny Recv-Q i Send-Q). </small> ~~Zadanie.#~~ Stwórz klienta TCP który: * łączy się pod podany adres * ustawia tryb nieblokujący * w pętli: * wysyła dane * wypisuje kolejny numer i ilość wysłanych danych * jeśli wysłał mniej danych niż chciał, kończy się <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.#~~ Napisz serwer 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 co odebrał ~~Zadanie.#~~a Napisz klienta TCP który wysyła cały alfabet po jednej literze. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ ~~Zadanie.#5~~b Napisz klienta TCP który wysyła wielokrotnie((10-20 razy)) po kilkanaście liter. 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.#~~ Napisz 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 napisz serwer 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'.\\ Przed uruchomieniem programu, zmień ustawienia swojego interfejsu sieciowego poleceniami: <html><div style="margin-top:-1.4em"></div></html> ip link set lo mtu 1500 tc qdisc add dev lo root netem delay 2ms 3ms 90% distribution pareto <html><div style="margin-top:-1.4em"></div></html> Aby wrócić do domyślnych ustawień, wpisz: <html><div style="margin-top:-1.4em"></div></html> ip link set lo mtu 65535 tc qdisc del dev lo root </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 ćwiczeń, możesz wpisać: <html><div style="margin-top:-1.4em"></html> <code> tc qdisc del root dev lo </code> <html></div></html> ~~Zadanie.#~~ Przygotuj program, który wyśle 1000 pakietów UDP zawierających kolejne liczby. ~~Zadanie.#~~ Wykonaj z roota poniższe polecenie, które spowoduje ograniczenie prędkości wysyłania pakietów i 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? ===== Opcje gniazd ===== Funkcja ''setsockopt'' pozwala dostosować zachowanie gniazd do potrzeb programisty. Bliźniacze ''getsockopt'' pozwala na odczyt opcji gniazd. \\ Podobnie jak np. ''ioctl'', funkcja ''setsockopt'' pozwala zmieniać zachowanie na wielu poziomach (np. gniazda / protokołu warstwy sieci / protokołu warstwy transportu). Drugi argument to właśnie wybór poziomu, trzeci - wybór opcji, czwarty to wskaźnik na wartość opcji, ostatni określa rozmiar przekazywanej opcji. \\ Parametry (opcje) które można ustawić są opisane w rozdziale 7 podręcznika systemowego. Przykłady opcji: * Ogólne opcje (''man 7 socket'', poziom ''SOL_SOCKET''): * ''SO_BROADCAST'' – wymagane do UDP broadcastu * ''SO_KEEPALIVE'' – włącza mechanizm TCP keepalive [[http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html]] * ''SO_LINGER'' – pozwala przed zamknięciem gniazda wysłać zakolejkowane dane * ''SO_RCVBUF'' i ''SO_SNDBUF'' – ustawienie rozmiaru systemowych buforów odbiorczych / nadawczych * ''SO_REUSEADDR'' – pozwala ignorować połączenia w stanie TIME_WAIT przy ustalaniu lokalnego adresu gniazda (wywoływaniu bind) [[https://tools.ietf.org/html/rfc793#page-22]] * Opcje protokołu IP (''man 7 ip'', poziom ''IPPROTO_IP''): * ''IP_ADD_MEMBERSHIP'' oraz ''IP_DROP_MEMBERSHIP'' – pozwala dodać siebie do grupy multicastowej * Opcje protokołu TCP (''man 7 tcp'', poziom ''IPPROTO_TCP''): * ''TCP_NODELAY'' – wyłącza algorytm Nagle'a (wyłącza buforowanie wyjścia jeśli danych jest mało a poprzednie nie zostały potwierdzone) ++++Przykłady zmiany opcji gniazd |<code cpp>// typowe zastosowanie – reuseaddr const int one = 1; setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); // przykład z innego poziomu - zmiana opcji TCP setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); // opcja której argumentem jest rozmiar size_t bufsize = 4*1024*1024; setsockopt(sockFd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)); // opcja której argumentem jest struktura struct sctp_event_subscribe events{.sctp_data_io_event=1, .sctp_association_event=1}; setsockopt(sockFe, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)); // przykład odczytania opcji uint32_t mtu; socklen_t optSize = sizeof(mtu); getsockopt(sockFd, IPPROTO_IP, IP_MTU, &mtu, &optSize); </code>++++ Funkcja ''fcntl'' (''man fcntl open'') pozwala na ustawienie (''F_SETFL'') opcji ''O_NONBLOCK'' potrzebnej do nieblokującej obsługi gniazd.
sk2/sockets_caveats.txt
· ostatnio zmienione: 2024/10/31 11:42 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry