Dydaktyka:
FeedbackTo jest stara wersja strony!
Schemat kolejności wywołać funkcji bibliotecznych znajduje się na:
http://www.cs.put.poznan.pl/ddwornikowski/sieci/sieci2/bsdsockets.html#rys-1
Aby oczekiwać na przychodzące połączenia TCP, po stworzeniu gniazda (socket(…)
) wystarczy wywołać funkcję listen(…)
, ale: jeśli wcześniej nie ustawi się adresu gniazda, system operacyjny wylosuje efemeryczny port i rozpocznie nasłuch na dowolnym adresie IP na tym porcie.
Dlatego przed funkcją listen(…)
należy użyć funkcji bind(…)
do ustawienia lokalnego adresu gniazda. Funkcja bind
przyjmuje jako argument strukturę sockaddr
.
Lokalny adres IP można ustawić na dowolny – INADDR_ANY
lub wybrany przez siebie – np. 127.0.0.1 (INADDR_LOOPBACK
) jeśli połączenia mają być ograniczone do localhosta.
Po wywołaniu funkcji listen
system operacyjny system operacyjny czeka na połączenia. Do odebrania nowego połączenia należy użyć funkcji accept(…)
. Funkcja accept zwróci nowe gniazdo reprezentujące nawiązane połączenie.
Zadanie 1: Napisz program, który:
bind
),listen
),accept
); na razie drugi i trzeci argument funkcji accept
pozostaw pusty,Zadanie 2: Zmodyfikuj program tak, by w pętli obsługiwał nowe połączenia
Funkcja accept
(podobnie jak wprowadzana za chwilę recvfrom
) może przekazać informację o adresie z którego nawiązano połączenie. W tym celu należy podać jej adres struktury sockaddr (drugi argument) i adres zmiennej, która w momencie wywołania accept
na wppisany rozmiar przekazanej struktury, a do której accept
zapisze ile bajtów przekazanej struktury wypełnił (trzeci argument).
Zadanie 3: Dodaj do programu wyświetlanie z jakiego adreesu IP i numeru portu nawiązano połączenie
TCP: Strumieniowy. Połączeniowy. Kontrola przepływu. Niezawodny (reliable).
UDP: Zorientowany na wiadomość. Bezpołączeniowy, bezstanowy. Brak kontroli przepływu. Best-effort.
Tworząc gniazdo UDP należy użyć następujących argumentów:
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) // lub: socket(PF_INET, SOCK_DGRAM, 0)
UDP nie nawiązuje połączenia – do odbioru i wysyłania wiadomości należy używać funkcji sendto
i recvfrom
(lub recv
/read
, jeśli nie obchodzi nas nadawca). W formie ułatwienia BSD socket API pozwala działać gniazdom UDP w trybie pseudo-połączeniowym - tzn. można wywołać funkcję connect
(która ustali adres odbiorcy) i dalej korzystać z send
/ write
.
UDP jest zorientowane na wiadomość – wysyła i odbiera całe datagramy. Jeśli funkcja odbierająca dane zadeklaruje mniejszy bufor niż rozmiar wiadomości, nadmiarowe dane zostaną odrzucone.
Zadanie 4. Odpowiedz na pytanie dlaczego DNS używa UDP zamiast TCP do zapytań.
Zadanie 5. Napisz program, który wyśle dane (stały ciąg znaków) pod wskazany adres, następnie odbierze dane i się zakończy.
Zadanie 6. Napisz program, który odbierze dane i odeśle je pod adres nadawcy zmieniając wielkość liter.
Jeśli nie wiesz jak zmienić wielkość liter, to for(char *it=str; (*it=toupper(*it)); ++it);
Zadanie 7. Napisz program, który będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawcó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, …)
).
Polecenia write
, send
,… może też zwrócić w pewnych okolicznościach mniejszy rozmiar wysłanych danych niż podany rozmiar buforu.
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
, … zapisują tylko część danych.
Dlatego zarówno przy odbieraniu jak i wysyłaniu trzeba zwracać uwagę na zwracaną ilość przetworzonych danych.
Zadanie 8. W serwerze TCP zmień rozmiar buforu nadawczego, przestaw gniazdo w tryb nieblokujący (kod poniżej) i każ wysłać ponad 4096 bajtów. Zaobserwuj co odbierze klient. Uwaga: klient musi być z innego hosta.
Nie wiesz jak wygenerować długi komunikat? ctrl+c, ctrl+v. Albo string s{"a"}; s.resize (22220,'='); s+="z";
Potem: s.c_str()
i s.length()
int buffsize = 1024; setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)); fcntl(socket, F_SETFL, O_NONBLOCK, 1);
Zadanie 9. W kliencie UDP i TCP zmniejsz rozmiar bufora przekazywanego do funkcji odbioru wiadomości i wykonaj odbiór dwa razy. Zaobserwuj co odbierze klient.
Strona podręcznika man 7 socket
opisuje możliwe argumenty funkcji setsockopt
i getsockopt
. Ważne opcje to:
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
– pomija stan TIME_WAIT po zamknięciu połączenia https://tools.ietf.org/html/rfc793#page-22
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
.)