====== 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'')((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''.)). 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 [[sk2:sockets_full:tcp_srv_img|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ótko((https://github.com/torvalds/linux/search?q=TCP_TIMEWAIT_LEN)) po zamknięciu programu serwera sprawdź poleceniem ''netstat -tpn'' oraz ''ss -atnop'' w jakim stanie jest połączenie. Przypomnij sobie co oznacza ten stan - [[https://tools.ietf.org/html/rfc793#page-22|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.