Narzędzia użytkownika

Narzędzia witryny


sk2:sockets_full

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Both sides previous revision Poprzednia wersja
Nowa wersja
Poprzednia wersja
sk2:sockets_full [2019/10/14 21:00]
jkonczak [Interface gniazd BSD (1/2)]
sk2:sockets_full [2024/10/09 15:17] (aktualna)
jkonczak [Interface gniazd BSD (1/2)]
Linia 1: Linia 1:
 ====== Interface gniazd BSD (1/2) ====== ====== Interface gniazd BSD (1/2) ======
-Schemat kolejności wywołać funkcji bibliotecznych:​ \\+Schemat kolejności wywołań funkcji bibliotecznych:​ \\
 <​html><​object id="​svg-object"​ data="/​jkonczak/​_media/​sk2:​sockets.svg"​ type="​image/​svg+xml"></​object></​html>​\\ <​html><​object id="​svg-object"​ data="/​jkonczak/​_media/​sk2:​sockets.svg"​ type="​image/​svg+xml"></​object></​html>​\\
 +-----
 +Funkcja ''​socket''​ i kolejność wywoływania funkcji z API gniazd na deskryptorze zwróconym przez tę funkcję:​\\ ​
 +<​html><​object id="​svg-object"​ data="/​jkonczak/​_media/​sk2:​gniazdo-diagram.svg"​ type="​image/​svg+xml"></​object></​html>​\\
 (Porównaj z: [[http://​www.cs.put.poznan.pl/​ddwornikowski/​sieci/​sieci2/​bsdsockets.html#​rys-1]].) (Porównaj z: [[http://​www.cs.put.poznan.pl/​ddwornikowski/​sieci/​sieci2/​bsdsockets.html#​rys-1]].)
 +
 ===== Serwer TCP ===== ===== 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. 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.\\ 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 ​adresu ​gniazda**. Funkcja ''​bind''​ przyjmuje jako argument strukturę ''​sockaddr''​.+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.+**Lokalny adres IP nasłuchującego gniazda zwykle ustawia się 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. \\ **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''​).+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. Do odebrania nowych połączeń używa się funkcji ''​accept(…)''​. **Funkcja ''​accept''​ zwraca nowe gniazdo** reprezentujące nawiązane połączenie.
  
-//Zadanie 1:// Napisz program, który:+<​html><​small><​/html>​Tworzenie kolejnych deskryptorów plików oraz wyniki ''​lsof'',​ ''​strace''​ i ''​ss''​ są zaprezentowane [[sk2:sockets_full:​tcp_srv_img|tutaj]]<​html><​/small><​/html> 
 + 
 +~~Zadanie.#​~~ ​Napisz program, który:
   * stworzy gniazdo,   * stworzy gniazdo,
-  * ustali adres (''​bind''​)uwaga: sprawdzaj wynik funkcji ''​bind'',​+  * ustali adres (''​bind''​) uwaga: sprawdzaj wynik funkcji ''​bind'' ​\\ <​small>​(jeśli dostaniesz błąd ''//​Address already in use//''​ na porcie na którym chwilę temu udało się uruchomić serwer, przeczytaj i wykonaj kolejne zadanie)</​small>​,
   * rozpocznie oczekiwanie na połączenia (''​listen''​),​   * rozpocznie oczekiwanie na połączenia (''​listen''​),​
     * ''​listen''​ jako drugi argument przyjmuje ile nowych połączeń może naraz czekać na odebranie funkcją accept     * ''​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'',​+  * 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),   * wyśle tam dane (stały ciąg znaków),
   * zakończy program.   * 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''​ 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.<code cpp>​const int one = 1;+~~Zadanie.#~~a 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.#​2~~b ​Ustaw przed wywołaniem ''​bind''​ opcję ''​SO_REUSEADDR''​ gniazda (kod poniżej) i powtórz zadanie 2a.<code cpp>​const int one = 1;
 setsockopt(sockFd,​ SOL_SOCKET, SO_REUSEADDR,​ &one, sizeof(one));</​code>​ setsockopt(sockFd,​ SOL_SOCKET, SO_REUSEADDR,​ &one, sizeof(one));</​code>​
 +<​small>​
 +Linux pozwala (przy włączonej odpowiedniej opcji w jądrze) usunąć dowolne połączenie sieciowe, też takie w stanie TIME-WAIT. Można do tego użyć np. komendy ''​ss''​ w połączeniu z filtrami i przełącznikiem ''​-kill''/''​-K'',​ np: ''​ss --kill --tcp src //​10.0.0.1//​ sport //2345// dst //​10.0.0.6//​ dport //​7890//''​
  
-<​html><​small></​html>​ +~~Zadanie.#~~ 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 ​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.<​html>​</small></​html>+</​small>​
  
-//Zadanie ​4:// Zmodyfikuj program tak, by w pętli obsługiwał nowe połączenia+~~Zadanie.#~~ 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ć: 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)   * gdzie ma zapisać ten adres – tj. podać adres struktury sockaddr (drugi argument)
-  * adres zmiennej, która w momencie wywołania ''​accept'' ​na wpisany rozmiar przekazanej struktury (trzeci argument)<​html><​small></​html><​code cpp>+  * adres zmiennej, która w momencie wywołania ''​accept'' ​ma wpisany rozmiar przekazanej struktury (trzeci argument)<​html><​small></​html><​code cpp>
 sockaddr_in nazwa_zmiennej;​ sockaddr_in nazwa_zmiennej;​
 socklen_t inna_zmienna = sizeof(nazwa_zmiennej);​ socklen_t inna_zmienna = sizeof(nazwa_zmiennej);​
-client = accept(ssock,​ &nazwa_zmiennej, &inna_zmienna);+client = accept(ssock, ​(sockaddr*)&nаzwa_zmiennej, &innа_zmienna);
 </​code><​html></​small></​html>​ </​code><​html></​small></​html>​
 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). 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+~~Zadanie.#~~ Dodaj do programu wyświetlanie z jakiego adresu IP i numeru portu nawiązano połączenie
 \\ \\
 <​html><​small></​html>​ <​html><​small></​html>​
 Funkcje zmieniające adres IP zapisany jako liczba na tekst to ''​inet_ntoa''​ i ''​inet_ntop''​ Funkcje zmieniające adres IP zapisany jako liczba na tekst to ''​inet_ntoa''​ i ''​inet_ntop''​
 <​html></​small></​html>​ <​html></​small></​html>​
 +
 +++++ Przykład minimalistycznego iteracyjnego serwera jednej gry w kółko i krzyżyk (bez sprawdzania warunku końca) |
 +<code c++>
 +#include <​sys/​socket.h>​
 +#include <​netinet/​in.h>​
 +#include <​cstdio>​
 +#include <​cstring>​
 +#include <​unistd.h>​
 +#include <​algorithm>​
 +
 +#define CHECK(code, txt) if(code==-1) {perror(txt);​ return 1;}
 +enum ttt:​char{e='​.',​X='​x',​O='​o'​} map[3][4]{{e,​e,​e},​{e,​e,​e},​{e,​e,​e}};​
 +#define MAP_TO_BUF sprintf(buf,​ "\n 012\n0%s\n1%s\n2%s\n\n",​\
 +                                (char*)map[0],​(char*)map[1],​(char*)map[2]);​
 +int main(){
 +    int s = socket(AF_INET,​ SOCK_STREAM,​ 0), one = 1;       ​CHECK(s,​ "​socket"​);​
 +    setsockopt(s,​ SOL_SOCKET, SO_REUSEADDR,​ &one, sizeof(one));​
 +    sockaddr_in saddr{AF_INET,​ htons(3003),​ {htonl(INADDR_ANY)}};​
 +    int rv = bind(s, (sockaddr*)&​saddr,​ sizeof(saddr)); ​     CHECK(rv, "​bind"​);​
 +    rv = listen(s, 1);                                     ​CHECK(rv,​ "​listen"​);​
 +    int c1 = accept(s, nullptr, nullptr); ​              ​CHECK(c1,​ "​accept c1");
 +    write(c1, "​Poczekaj na drugiego gracza\n",​ 28);
 +    int c2 = accept(s, nullptr, nullptr); ​              ​CHECK(c2,​ "​accept c2");
 +    close(s);
 +    for(;;​std::​swap(c1,​c2)){
 +        write(c2, "Ruch przeciwnika.\n",​ 18);
 +        write(c1, "Podaj x i y oddzielone spacją (zakres 0-2)\n",​ 44);
 +        char buf[256]{}; unsigned x=3, y=3;
 +        read(c1, buf, 255);
 +        sscanf(buf, "​%u%u",​ &x, &y);
 +        if (x<​3&&​y<​3 && map[y][x]==e) map[y][x]=c1<​c2?​X:​O;​ MAP_TO_BUF;
 +        rv = write(c2, buf, 22);         // program zakończy się kiedy dostanie
 +        rv = write(c1, buf, 22);         // sygnał SIGPIPE przy próbie zapisu
 +    }                                    // do zamniętego gniazda
 +}
 +</​code>​
 +++++
 +
 ===== UDP vs TCP - przypomnienie ==== ===== UDP vs TCP - przypomnienie ====
  
Linia 69: Linia 118:
 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. 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ń.+~~Zadanie.#~~ Odpowiedz na pytanie dlaczego DNS używa UDP zamiast TCP do zapytań.
  
 ===== UDP ===== ===== UDP =====
Linia 80: Linia 129:
  
 UDP jest bezpołączeniowe,​ więc nie da się na nim wykonać funkcji ''​shutdown''​. UDP jest bezpołączeniowe,​ więc nie da się na nim wykonać funkcji ''​shutdown''​.
- 
 ==== Klient UDP ==== ==== 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.+~~Zadanie.#~~ 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 ==== ==== 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.\\+~~Zadanie.#~~ Napisz program, który korzystając z protokołu UDP odbierze dane i odeśle je pod adres nadawcy zmieniając wielkość liter.\\
 <​html><​small></​html>​Jeśli nie wiesz jak zmienić wielkość liter, to ''​for(char *it=str; (*it=toupper(*it));​ ++it);''<​html></​small></​html>​ <​html><​small></​html>​Jeśli nie wiesz jak zmienić wielkość liter, to ''​for(char *it=str; (*it=toupper(*it));​ ++it);''<​html></​small></​html>​
  
-//Zadanie ​9.// Napisz program, który będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawców.+~~Zadanie.#~~ Napisz program, który będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawców.
  
  
sk2/sockets_full.1571079659.txt.gz · ostatnio zmienione: 2019/10/14 21:00 przez jkonczak