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 [2017/11/17 23:31]
jkonczak [UDP vs TCP - przypomnienie]
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 ​znajduje się na: \\ [[http://​www.cs.put.poznan.pl/​ddwornikowski/​sieci/​sieci2/​bsdsockets.html#​rys-1]]+Schemat kolejności wywołań funkcji bibliotecznych: \\ 
 +<​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]].) 
 ===== Serwer TCP ===== ===== Serwer TCP =====
-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.\\ +Aby oczekiwać na przychodzące połączenia TCP konieczne jest stworzenie gniazda do odbierania nowych połączeń. Takie gniazdotzw. **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. 
-Dlatego przed funkcją ''​listen(…)'' ​używa się funkcji ​''​bind(…)'' ​do ustawienia lokalnego adresu ​gniazda. Funkcja ''​bind''​ przyjmuje jako argument strukturę ''​sockaddr''​.+ 
 +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 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''​.)).
  
-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.+Do odebrania nowych połączeń używa się funkcji ​''​accept(…)''​**Funkcja ​''​accept'' ​zwraca nowe gniazdo** reprezentujące nawiązane ​połączenie.
  
-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.+<​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 ​1:// Napisz program, który:+~~Zadanie.#~~ Napisz program, który:
   * stworzy gniazdo,   * stworzy gniazdo,
-  * ustali adres (''​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+  * 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;​ 
 +socklen_t inna_zmienna = sizeof(nazwa_zmiennej);​ 
 +client = accept(ssock,​ (sockaddr*)&​nаzwa_zmiennej,​ &​innа_zmienna);​ 
 +</​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>​ 
 +Funkcje zmieniające adres IP zapisany jako liczba na tekst to ''​inet_ntoa''​ i ''​inet_ntop''​ 
 +<​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 49: Linia 112:
 sockaddr_in nazwa_zmiennej {...}; sockaddr_in nazwa_zmiennej {...};
 socklen_t inna_zmienna = sizeof(nazwa_zmiennej);​ socklen_t inna_zmienna = sizeof(nazwa_zmiennej);​
-kolejna_zmienna = sendto("​wiadomosc",​ 9, 0, &​nazwa_zmiennej,​ inna_zmienna);​ +kolejna_zmienna =   ​sendto(sockfd, ​"​wiadomosc", ​             9, 0, (sockaddr*) ​&​nazwa_zmiennej, ​ inna_zmienna);​ 
-kolejna_zmienna = recvfrom(bufor,​ rozmiar_bufora,​ &​nazwa_zmiennej,​ &​inna_zmienna);​+kolejna_zmienna = recvfrom(sockfd, ​      bufor, rozmiar_bufora, ​0, (sockaddr*) ​&​nazwa_zmiennej,​ &​inna_zmienna);​
 </​code><​html></​small></​html>​ </​code><​html></​small></​html>​
  
 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 ===== 
 + 
 +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''​.
  
-===== Klient UDP =====+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.+~~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.1510957870.txt.gz · ostatnio zmienione: 2017/11/17 23:31 przez jkonczak