Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_caveats [2019/10/15 14:06] jkonczak [Opcje gniazd] |
sk2:sockets_caveats [2023/10/17 12:45] (aktualna) jkonczak [Opcje gniazd] |
||
---|---|---|---|
Linia 6: | Linia 6: | ||
**{{:sk2:l3.tar.xz|Kody źródłowe zadań 1-9,12}}** | **{{:sk2:l3.tar.xz|Kody źródłowe zadań 1-9,12}}** | ||
- | <html><small></html>Aby zbudować programy ręcznie, wykonaj:<code>mkdir build | + | <html><small></html> |
+ | Uwaga: jeśli korzystasz z innej biblioteki niż glibc, ściągnij też plik {{:sk2:error.h|}} - w programach jest użyte niestandardowa funkcja [[https://linux.die.net/man/3/error|error]] z glibc, a podlinkowany plik jest uproszczoną implementacją tej funkcji. | ||
+ | |||
+ | Aby ściągnąć i zbudować programy ręcznie, wykonaj:<code>wget -O - http://www.cs.put.poznan.pl/jkonczak/_media/sk2:l3.tar.xz | tar xvJ | ||
+ | cd l3 | ||
+ | |||
+ | mkdir build | ||
cd build | cd build | ||
cmake .. | cmake .. | ||
Linia 14: | Linia 20: | ||
Programy serwera i klienta można uruchamiać na jednym komputerze, łącząc się z klienta na adres 127.0.0.1 lub localhost. | Programy serwera i klienta można uruchamiać na jednym komputerze, łącząc się z klienta na adres 127.0.0.1 lub localhost. | ||
- | ===== Read i Write - ilość odczytanych / zapisanych danych ===== | + | ===== Blokowanie i wartości zwracane przez funkcje odbierające / wysyłające dane ===== |
+ | ==== Read i Write - ilość odczytanych / zapisanych bajtów ==== | ||
- | Polecenie ''read'', ''recv'',… często zwracają mniej danych niż podana w argumentach wielkość bufora. Powód? Mniej danych przyszło lub skończyło się miejsce w buforze odbiorczym (''setsockopt(socket, SOL_SOCKET, SO_RCVBUF, …)''). | + | 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. |
- | Polecenia ''write'', ''send'',… może też zwrócić w pewnych okolicznościach mniejszy rozmiar wysłanych danych niż podany rozmiar buforu. | + | Funkcje ''write'', ''send'',… mogą też w pewnych okolicznościach wysłać mniejszą ilość bajtów niż zażądano. |
- | Jest to spowodowane zapchaniem 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 ustawi się opcję ''O_NONBLOCK'' (typowa w wielu zastosowaniach), to funkcje takie jak ''write'', ''send'', … zapiszą tylko część danych. | + | 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 zwracać uwagę na zwracaną ilość przetworzonych danych. | + | 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/9699919799/basedefs/V1_chap03.html#tag_03_77|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"></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). | ||
+ | <html></div></html> | ||
+ | |||
+ | 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.4em 0 -1.4em 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.4em"></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> | ||
+ | W Linuksie można ustawiać tryb nieblokujący wykorzystując też niebędące częścią standardu POSIX rozszerzenia: 1) jeżeli ostatni argument funkcji ''socket'' będzie zsumowany z ''SOCK_NONBLOCK'', to nowo stworzone gniazdo będzie w trybie nieblokującym (''[[https://man7.org/linux/man-pages/man2/socket.2.html|man 2 socket]]''), 2) jeżeli zamiast funkcji ''accept'' wykorzysta się odpowiednik tej funkcji rozszerzony o pole flag – ''accept4'' – i poda flagę ''SOCK_NONBLOCK'' (''[[https://man7.org/linux/man-pages/man2/accept4.2.html|man 2 accept4]]'') | ||
+ | </small> | ||
+ | ==== Zadania ===== | ||
//Zadanie 1.// 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 1.// 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ń). | ||
Linia 38: | Linia 77: | ||
//Zadanie 3.// Stwórz klienta TCP który: | //Zadanie 3.// Stwórz klienta TCP który: | ||
* łączy się pod podany adres | * łączy się pod podany adres | ||
- | * ustawia tryb nieblokujący (gotowy kod: ''fcntl(sockFd, F_SETFL, O_NONBLOCK, 1);'' ) | + | * ustawia tryb nieblokujący |
* w pętli: | * w pętli: | ||
* wysyła dane | * wysyła dane | ||
* wypisuje kolejny numer i ilość wysłanych danych | * wypisuje kolejny numer i ilość wysłanych danych | ||
* jeśli wysłał mniej danych niż chciał, kończy się | * jeśli wysłał mniej danych niż chciał, kończy się | ||
- | Połącz klienta do serwera z zadania 1.((W tym zadaniu waro porównac wynik dla klienta z lokalnego komputera i ze zdalnego komputera)) | + | 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ść ==== | + | ===== Protokół strumieniowy / zorientowany na wiadomość ===== |
//Zadanie 4.// Napisz serwer TCP który po odebraniu połączenia, do jego zamknięcia, wykonuje w pętli: | //Zadanie 4.// Napisz serwer TCP który po odebraniu połączenia, do jego zamknięcia, wykonuje w pętli: | ||
Linia 52: | Linia 91: | ||
//Zadanie 5a.// Napisz klienta TCP który wysyła cały alfabet po jednej literze. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ | //Zadanie 5a.// Napisz klienta TCP który wysyła cały alfabet po jednej literze. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ | ||
- | //Zadanie 5b.// Napisz klienta TCP który wysyła wielokrotnie((20-50 razy)) po kilkanaście liter. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ | + | //Zadanie 5b.// Napisz klienta TCP który wysyła wielokrotnie((10-20 razy)) po kilkanaście liter. Podłącz się (wielokrotnie) do serwera z zadania 4. \\ |
<html><small></html>(W zadaniach 5a i 5b łącz się do swojego komputera.)<html></small></html> | <html><small></html>(W zadaniach 5a i 5b łącz się do swojego komputera.)<html></small></html> | ||
Linia 65: | Linia 104: | ||
===== Kolejność danych i (nie)zawodność ===== | ===== Kolejność danych i (nie)zawodność ===== | ||
//Zadanie 10.// Wykonaj z roota poniższe polecenie, które spowoduje pomieszanie kolejności pakietów wysłanych przez interfejs ''lo'': | //Zadanie 10.// Wykonaj z roota poniższe polecenie, które spowoduje pomieszanie kolejności pakietów wysłanych przez interfejs ''lo'': | ||
+ | <html><div style="margin-top:-1.4em"></html> | ||
<code> | <code> | ||
tc qdisc add dev lo root netem delay 5ms 5ms distribution normal loss 10% | tc qdisc add dev lo root netem delay 5ms 5ms distribution normal loss 10% | ||
</code> | </code> | ||
+ | <html></div></html> | ||
- | Aby przywrócić domyślne zachowanie, wpisz: | + | Aby przywrócić domyślne zachowanie po wykonaniu ćwiczeń, możesz wpisać: |
+ | <html><div style="margin-top:-1.4em"></html> | ||
<code> | <code> | ||
tc qdisc del root dev lo | tc qdisc del root dev lo | ||
</code> | </code> | ||
+ | <html></div></html> | ||
//Zadanie 11.// Uruchom ponownie programy z zadań 5 i 7. | //Zadanie 11.// Uruchom ponownie programy z zadań 5 i 7. | ||
Linia 79: | Linia 122: | ||
//Zadanie 13.// Wykonaj z roota poniższe polecenie, które spowoduje ograniczenie prędkości wysyłania pakietów i przetestuj program z poprzedniego zadania: | //Zadanie 13.// 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> | <code> | ||
tc qdisc add dev lo root tbf rate 10kbps burst 1.5kb limit 10kb | tc qdisc add dev lo root tbf rate 10kbps burst 1.5kb limit 10kb | ||
</code> | </code> | ||
+ | <html></div></html> | ||
+ | |||
+ | <html><small></html> | ||
+ | |||
+ | 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'' | ||
+ | |||
+ | <html></small></html> | ||
//Zadanie 14.// Jak w protokole UDP radzić sobie z kolejnością pakietów? Jak radzić sobie ze zgubieniem pakietów? | //Zadanie 14.// Jak w protokole UDP radzić sobie z kolejnością pakietów? Jak radzić sobie ze zgubieniem pakietów? | ||
Linia 88: | Linia 140: | ||
Funkcja ''setsockopt'' pozwala dostosować zachowanie gniazd do potrzeb programisty. Bliźniacze ''getsockopt'' pozwala na odczyt opcji 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 wartość opcji. \\ | + | 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. Poniżej lista najważniejszych: | + | 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''): | * Ogólne opcje (''man 7 socket'', poziom ''SOL_SOCKET''): | ||
* ''SO_BROADCAST'' – wymagane do UDP broadcastu | * ''SO_BROADCAST'' – wymagane do UDP broadcastu | ||
Linia 100: | Linia 152: | ||
* Opcje protokołu TCP (''man 7 tcp'', poziom ''IPPROTO_TCP''): | * 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) | * ''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. (''O_NONBLOCK'' można też ustawić sumując ostatni argument funkcji ''socket'' z ''SOCK_NONBLOCK''.) | + | Funkcja ''fcntl'' (''man fcntl open'') pozwala na ustawienie (''F_SETFL'') opcji ''O_NONBLOCK'' potrzebnej do nieblokującej obsługi gniazd. |