Dydaktyka:
FeedbackTo jest stara wersja strony!
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:
Aby zbudować programy ręcznie, wykonaj:
mkdir build cd build 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.
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 odbiorczym1). 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 skończy się miejsce.
Dlatego zarówno przy odbieraniu jak i wysyłaniu trzeba sprawdzać zwracaną ilość przetworzonych danych.
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. Przykłady takich funkcji 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. Przykłady takich funkcji (sieciowych) to socket
, bind
, listen
, setsockopt
, gethostbyname
/ getaddrinfo
(o ile funkcja nie odpytywała serwera nazw).
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
2). Gniazdo przestawia się w tryb nieblokujący podobnie jak każdy inny deskryptor pliku:
fcntl(fileDescriptor, F_SETFL, O_NONBLOCK, 1);
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);
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 2. Stwórz klienta TCP który:
Połącz klienta do serwera z zadania 1.
Możesz sprawdzić poleceniem netstat
, np. netstat -tn
, ile bajtów jest w buforze odbiorczym/nadawczym (Recv-Q i Send-Q)
Zadanie 3. Stwórz klienta TCP który:
Połącz klienta do serwera z zadania 1.3)
Zadanie 4. Napisz serwer TCP który po odebraniu połączenia, do jego zamknięcia, wykonuje w pętli:
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 wielokrotnie4) po kilkanaście liter. Podłącz się (wielokrotnie) do serwera z zadania 4.
(W zadaniach 5a i 5b łącz się do swojego komputera.)
Zadanie 6. Powtórz zadanie 4 dla UDP.
Zadanie 7. Powtórz zadanie 5a i 5b dla UDP.
Zadanie 8. Jak w strumieniu danych – czyli w połączeniu TCP – przesyłać dane tak by serwer wiedział kiedy cała wiadomość dotrze?
Zadanie 9. Jak w protokole UDP zapewnić, że cała wiadomość została odebrana?
Zadanie 10. Wykonaj z roota poniższe polecenie, które spowoduje pomieszanie kolejności pakietów wysłanych przez interfejs lo
:
tc qdisc add dev lo root netem delay 5ms 5ms distribution normal loss 10%
Aby przywrócić domyślne zachowanie, wpisz:
tc qdisc del root dev lo
Zadanie 11. Uruchom ponownie programy z zadań 5 i 7.
Zadanie 12. Przygotuj program, który wyśle 1000 pakietów UDP zawierających kolejne liczby.
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:
tc qdisc add dev lo root tbf rate 10kbps burst 1.5kb limit 10kb
Zadanie 14. Jak w protokole UDP radzić sobie z kolejnością pakietów? Jak radzić sobie ze zgubieniem pakietów?
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:
man 7 socket
, poziom SOL_SOCKET
):SO_BROADCAST
– wymagane do UDP broadcastuSO_KEEPALIVE
– włącza mechanizm TCP keepalive http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.htmlSO_LINGER
– pozwala przed zamknięciem gniazda wysłać zakolejkowane daneSO_RCVBUF
i SO_SNDBUF
– ustawienie rozmiaru systemowych buforów odbiorczych / nadawczychSO_REUSEADDR
– pozwala ignorować połączenia w stanie TIME_WAIT przy ustalaniu lokalnego adresu gniazda (wywoływaniu bind) https://tools.ietf.org/html/rfc793#page-22man 7 ip
, poziom IPPROTO_IP
):IP_ADD_MEMBERSHIP
oraz IP_DROP_MEMBERSHIP
– pozwala dodać siebie do grupy multicastowej 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)// 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);
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
.)
setsockopt(socket, SOL_SOCKET, SO_RCVBUF, …)
connect
, która rozpoczyna nawiązywanie połączenia w tle, zwraca -1
i ustawia errno
na EINPROGRESS
.