Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:sockets_full

Interface gniazd BSD (1/2)

Schemat kolejności wywołać funkcji bibliotecznych:

(Porównaj z: http://www.cs.put.poznan.pl/ddwornikowski/sieci/sieci2/bsdsockets.html#rys-1.)

Serwer TCP

Aby oczekiwać na przychodzące połączenia TCP konieczne jest stworzenie gniazda do odbierania nowych połączeń. Takie gniazdo, tzw. gniazdo nasłuchujące (listening), nie umożliwia odbierania ani wysyłania danych - pozwala tylko na odbieranie przychodzących połączeń i tworzy dla każdego nowego połączenia kolejne gniazdo reprezentujące to odebrane połączenie.

Gniazdo nasłuchujące tworzy się wywołując po stworzeniu gniazda (socket(…)) 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 funkcją bind(…) ustawić lokalny adres gniazda. Funkcja bind przyjmuje jako argument strukturę sockaddr.

Lokalny adres IP nasłuchującego gniazda zwykle ustawia się na ustawić na dowolny – INADDR_ANY. Inny lokalny adres IP ustawia się, jeśli gniazdo ma odbierać połączenia kierowane tylko na ten adres – np. 127.0.0.1 (INADDR_LOOPBACK) jeśli połączenia mają być ograniczone do połączeń z localhosta.

Wywołanie funkcji listen nakazuje systemowi operacyjnemu czekać na połączenia (porównaj z: connect). Wykonanie funkcji listen jest natychmiastowe – ta funkcja nie czeka na połączenie, tylko informuje system operacyjny że nowe połączenia przychodzące na ustawiony adres mają być kierowane na to gniazdo.
Argumentem funkcji listen jest ilość nowych połączeń które czekają w kolejce na odebranie (tj. połączeń dla których nie wykonano jeszcze funkcji accept)1).

Do odebrania nowych połączeń używa się funkcji accept(…). Funkcja accept zwraca nowe gniazdo reprezentujące nawiązane połączenie.

Tworzenie kolejnych deskryptorów plików oraz wyniki lsof, strace i ss są zaprezentowane tutaj

Zadanie 1: Napisz program, który:

  • stworzy gniazdo,
  • ustali adres (bind), uwaga: sprawdzaj wynik funkcji bind,
  • rozpocznie oczekiwanie na połączenia (listen),
    • listen jako drugi argument przyjmuje ile nowych połączeń może naraz czekać na odebranie funkcją accept
  • zaakceptuje połączenie (accept); na razie drugi i trzeci argument funkcji accept ustaw na 0 lub nullptr,
  • wyśle tam dane (stały ciąg znaków),
  • zakończy program.

Zadanie 2a: Krótko2) po zamknięciu programu serwera sprawdź poleceniem netstat -tpn oraz ss -atnop w jakim stanie jest połączenie. Przypomnij sobie co oznacza ten stan - RFC793. Spróbuj w tym czasie uruchomić ponownie program serwera.
Zadanie 2b: Ustaw przed wywołaniem bind opcję SO_REUSEADDR gniazda (kod poniżej) i powtórz zadanie 2a.

const int one = 1;
setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

Zadanie 3: Zakomentuj wywołanie bind, uruchom program i odczytaj z wyników komendy netstat -tlpn na jakim porcie nasłuchuje twój serwer. Następnie połącz się do niego.

Zadanie 4: 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 jej podać:

  • gdzie ma zapisać ten adres – tj. podać adres struktury sockaddr (drugi argument)
  • adres zmiennej, która w momencie wywołania accept ma wpisany rozmiar przekazanej struktury (trzeci argument)
    sockaddr_in nazwa_zmiennej;
    socklen_t inna_zmienna = sizeof(nazwa_zmiennej);
    client = accept(ssock, (sockaddr*)&nаzwa_zmiennej, &innа_zmienna);

Jeśli drugi i trzeci argument są niezerowe, accept wypisuje przekazaną strukturę (drugi argument) i ustawia ile bajtów przekazanej struktury wypełnił (trzeci argument).

Zadanie 5: Dodaj do programu wyświetlanie z jakiego adresu IP i numeru portu nawiązano połączenie
Funkcje zmieniające adres IP zapisany jako liczba na tekst to inet_ntoa i inet_ntop

UDP vs TCP - przypomnienie

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.

Przykład użycia funkcji sendto i recvfrom:

sockaddr_in nazwa_zmiennej {...};
socklen_t inna_zmienna = sizeof(nazwa_zmiennej);
kolejna_zmienna =   sendto(sockfd, "wiadomosc",              9, 0, (sockaddr*) &nazwa_zmiennej,  inna_zmienna);
kolejna_zmienna = recvfrom(sockfd,       bufor, rozmiar_bufora, 0, (sockaddr*) &nazwa_zmiennej, &inna_zmienna);

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 6. Odpowiedz na pytanie dlaczego DNS używa UDP zamiast TCP do zapytań.

UDP

Program korzystający z UDP musi stworzyć funkcją socket gniazdu typu SOCK_DGRAM.

Następnie program może (a jeśli ma pełnić rolę serwera, to powinien) wywołać funkcję bind, ustalając lokalny numer portu.

Gniazdo UDP w każdym momencie po stworzeniu może wykonywać operacje odbioru i wysyłania danych – sendto, recvfrom, recv, read.

UDP jest bezpołączeniowe, więc nie da się na nim wykonać funkcji shutdown.

Klient UDP

Zadanie 7. Napisz program, który korzystając z protokołu UDP wyśle dane (stały ciąg znaków) pod wskazany adres, następnie odbierze dane i się zakończy.

Serwer UDP

Zadanie 8. Napisz program, który korzystając z protokołu UDP 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 9. Napisz program, który będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawców.

1) Argument "backlog" funkcji listen powinien się zawierać w zakresie 1÷SOMAXCONN (w tej chwili równe 128 w Linuksie) i jest traktowany jako podpowiedź, tzn. system operacyjny może używać innego limitu niż podany w argumencie listen.
sk2/sockets_full.txt · ostatnio zmienione: 2022/10/10 19:19 przez jkonczak