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
Nowa wersja Both sides next revision
sk2:sockets_full [2016/10/19 18:24]
jkonczak [Read i Write - ilość odczytanych / zapisanych danych]
sk2:sockets_full [2022/10/10 16:36]
jkonczak
Linia 1: Linia 1:
-====== Interface gniazd BSD ====== +====== 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>​\\ 
 +(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 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.
-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.+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''​.
  
-Po wywołaniu funkcji ''​listen'' ​system operacyjny system operacyjny ​czeka na połączenia. Do odebrania ​nowego ​połączenia należy ​yć funkcji ''​accept(…)''​. Funkcja accept ​zwróci ​nowe gniazdo reprezentujące nawiązane połączenie. ​ +**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''​). 
 + 
 +Do odebrania ​nowych ​połączeń ywa się funkcji ''​accept(…)''​. ​**Funkcja ​''​accept''​ zwraca ​nowe gniazdo** reprezentujące nawiązane połączenie. 
 + 
 +<​html><​small></​html>​Tworzenie kolejnych deskryptorów plików oraz wyniki strace/ss są zaprezentowane [[sk2:​sockets_full:​tcp_srv_img|tutaj]]<​html></​small></​html>​
  
 //Zadanie 1:// Napisz program, który: //Zadanie 1:// Napisz program, który:
   * stworzy gniazdo,   * stworzy gniazdo,
-  * ustali adres (''​bind''​),​+  * ustali adres (''​bind''​), uwaga: sprawdzaj wynik funkcji ''​bind''​,
   * rozpocznie oczekiwanie na połączenia (''​listen''​),​   * rozpocznie oczekiwanie na połączenia (''​listen''​),​
-  ​* zaakceptuje połączenie (''​accept''​);​ na razie drugi i trzeci argument funkcji ''​accept'' ​pozostaw pusty,+    * ''​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),   * wyśle tam dane (stały ciąg znaków),
   * zakończy program.   * zakończy program.
  
-//​Zadanie ​2:// Zmodyfikuj program tak, by w pętli obsługiwał nowe połączenia+//​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.<code cpp>​const int one = 1; 
 +setsockopt(sockFd,​ SOL_SOCKET, SO_REUSEADDR,​ &one, sizeof(one));</​code>​
  
-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).+<​html><​small></​html>​ 
 +//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>​
  
-//​Zadanie ​3:// Dodaj do programu wyświetlanie z jakiego adreesu IP i numeru portu nawiązano ​połączenie+//​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)<​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).
 +
 +//Zadanie 5:// 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>​
 ===== UDP vs TCP - przypomnienie ==== ===== UDP vs TCP - przypomnienie ====
  
Linia 34: Linia 60:
 </​code>​ </​code>​
  
-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 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'':​ <​html><​small></​html><​code cpp> 
 +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);​ 
 +</​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 ​4.// Odpowiedz na pytanie dlaczego DNS używa UDP zamiast TCP do zapytań.+//​Zadanie ​6.// Odpowiedz na pytanie dlaczego DNS używa UDP zamiast TCP do zapytań.
  
-===== Klient ​UDP =====+===== UDP =====
  
-//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.+Program korzystający z UDP musi stworzyć funkcją ''​socket''​ gniazdu typu ''​SOCK_DGRAM''​.
  
-===== Serwer UDP =====+Następnie program może (a jeśli ma pełnić rolę serwera, to powinien) wywołać funkcję ''​bind'',​ ustalając lokalny numer portu.
  
-//Zadanie 6.// Napisz program, który odbierze dane i odeśle je pod adres nadawcy zmieniając wielkość liter.\\ +Gniazdo UDP w każdym momencie po stworzeniu może wykonywać operacje odbioru i wysyłania danych – ''​sendto''​, ''​recvfrom''​, ''​recv'',​ ''​read''​.
-<​html><​small></​html>​Jeśli nie wiesz jak zmienić wielkość literto ''​for(char *it=str; (*it=toupper(*it));​ ++it);''​<​html></​small></​html>​+
  
-//Zadanie 7.// Napisz programktóry będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawców.+UDP jest bezpołączeniowewięc nie da się na nim wykonać funkcji ''​shutdown''​. 
 +==== Klient UDP ====
  
-===== Read i Write - ilość odczytanych ​zapisanych danych ​ =====+//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.
  
-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, …)''​).+==== Serwer UDP ====
  
-Polecenia ''​write'',​ ''​send'',​… może też zwrócić w pewnych okolicznościach mniejszy rozmiar wysłanych danych niż podany rozmiar buforu. +//Zadanie 8.// Napisz programktóry korzystając z protokołu UDP odbierze ​dane i odeśle je pod adres nadawcy zmieniając wielkość liter.\\ 
-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. +<​html><​small></​html>​Jeśli nie wiesz jak zmienić wielkość literto ''​for(char *it=str; (*it=toupper(*it)); ++it);''<​html></​small></​html>​
- +
-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 nadawczegoprzestaw 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. \\ <​html><​small></​html>​Nie wiesz jak wygenerować długi komunikat? ctrl+cctrl+v. Albo ''​string s{"​a"​}s.resize ​(22220,'​='); s+="​z"​;''​ Potem: ''​s.c_str()''​ i ''​s.length()''<​html></​small></​html>​ +
-<code cpp> +
-int buffsize = 1024; +
-setsockopt(socket,​ SOL_SOCKET, SO_SNDBUF, &​buffsize,​ sizeof(buffsize));​ +
-fcntl(socket,​ F_SETFL, O_NONBLOCK, 1); +
-</​code>​ +
- +
-//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. +
- +
-===== Opcje gniazd =====+
  
-Strona podręcznika ''​man 7 socket''​ opisuje możliwe argumenty funkcji ''​setsockopt''​ i ''​getsockopt''​. Ważne opcje to: +//Zadanie 9.// Napisz program, który będzie w pętli odbierać dane i odsyłać je pod adresy wszystkich wcześniejszych nadawców.
-  *  ''​SO_BROADCAST''​ - wymagane do UDP broadcastu +
-  *  ''​SO_KEEPALIVE''​ - włącza mechanizm TCP keepalive [[http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/​overview.html]] +
-  * ''​SO_LINGER''​ – pozwala przed zamknięciem gniazda wysłać zakolejkowane ​dane +
-  * ''​SO_RCVBUF'' ​''​SO_SNDBUF''​ – ustawienie rozmiaru systemowych buforów odbiorczych / nadawczych +
-  * ''​SO_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''​.) 
  
sk2/sockets_full.txt · ostatnio zmienione: 2022/10/10 19:19 przez jkonczak