Narzędzia użytkownika

Narzędzia witryny


sk2:sockets_concurrency

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_concurrency [2018/10/28 22:24]
jkonczak [Wiele wątków]
sk2:sockets_concurrency [2023/11/09 12:30] (aktualna)
jkonczak [Funkcje biblioteczne]
Linia 2: Linia 2:
 Typowo aplikacje (jakiekolwiek,​ włączając sieciowe) muszą jednocześnie obsługiwać wiele źródeł zdarzeń (np. dane gotowe do odbioru, użytkownik kliknął na menu, naciśnięto enter). Typowo aplikacje (jakiekolwiek,​ włączając sieciowe) muszą jednocześnie obsługiwać wiele źródeł zdarzeń (np. dane gotowe do odbioru, użytkownik kliknął na menu, naciśnięto enter).
 Domyślnie operacje na gniazdach (np. ''​connect'',​ ''​accept'',​ ''​read''​) blokują przetwarzanie. Domyślnie operacje na gniazdach (np. ''​connect'',​ ''​accept'',​ ''​read''​) blokują przetwarzanie.
-Można to zmienić (tak jak dla każdego innego pliku w Linuksie) na obsługę nieblokującą,​ ale oczywiście aktywne czekanie ([[https://​en.wikipedia.org/​wiki/​Busy_waiting]]) jest bardzo głupim pomysłem.+Można to zmienić (tak jak dla każdego innego pliku w Linuksie) na obsługę nieblokującą,​ ale oczywiście aktywne czekanie ([[https://​en.wikipedia.org/​wiki/​Busy_waiting]]) jest bardzo głupim pomysłem((choć są od tego wyjątki - np. [[https://​en.wikipedia.org/​wiki/​Spinlock|spinlock]] czy [[https://​www.dpdk.org/​|DPDK]])).
  
-Do obsługi wielu źródeł zdarzeń stworzono ​dedykowanych ​metody, można też używać typowych metod pisania aplikacji współbieżnych. Zwolennicy SE nazwą to "​wzorcami projektowymi"​. Przegląd typowych metod tworzenia aplikacji sieciowych:+Do obsługi wielu źródeł zdarzeń stworzono ​dedykowane ​metody, można też używać typowych metod pisania aplikacji współbieżnych. Zwolennicy SE nazwą to "​wzorcami projektowymi"​. Przegląd typowych metod tworzenia aplikacji sieciowych:
   * Iteracyjnie – kiedy współbieżność jest zbędna.   * Iteracyjnie – kiedy współbieżność jest zbędna.
-  * Pętla zdarzeń (event loop, [[https://​en.wikipedia.org/​wiki/​Event_loop|[1]]],​ [[https://​pl.wikipedia.org/​wiki/​Programowanie_sterowane_zdarzeniami|[2]]]) – programista wpierw przygotowuje kod (funkcje) obsługi możliwych zdarzeń, następnie w pętli czeka na zdarzenie i wywołuje kod powiązany ze zdarzeniem. Aplikacja może być jedno- lub wielowątkowa,​ muszą być dostępne funkcje czekające na zdarzenie – dla I/O pod Linuksem to ''​select'',​ ''​poll''​ i ''​epoll''​. Praktycznie wszystkie programy z GUI wykorzystują pętlę zdarzeń przynajmniej do obsługi GUI.+  * Pętla zdarzeń (event loop, [[https://​en.wikipedia.org/​wiki/​Event_loop|[1]]],​ [[https://​web.archive.org/​web/​20190730174916/​https://​pl.wikipedia.org/​wiki/​Programowanie_sterowane_zdarzeniami|[2]]]) – programista wpierw przygotowuje kod (funkcje) obsługi możliwych zdarzeń, następnie w pętli czeka na zdarzenie i wywołuje kod powiązany ze zdarzeniem. Aplikacja może być jedno- lub wielowątkowa,​ muszą być dostępne funkcje czekające na zdarzenie – dla I/O pod Linuksem to ''​select'',​ ''​poll''​ i ''​epoll''​. Praktycznie wszystkie programy z GUI wykorzystują pętlę zdarzeń przynajmniej do obsługi GUI.
   * Aplikacja wielowątkowa – każde źródło zdarzeń – np. gniazdo – jest obsługiwane w osobnym wątku.   * Aplikacja wielowątkowa – każde źródło zdarzeń – np. gniazdo – jest obsługiwane w osobnym wątku.
 ====== Wiele wątków ====== ====== Wiele wątków ======
  
-[[sk2:​cpp11_threads|Wątki w C++11]]+[[sk2:​cpp11_threads|Wątki w C++]]
  
-//Zadanie 1.// Napisz klienta, który nawiąże ​jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości.\\ (Możesz skorzystać z {{:​sk2:​tcp_client_template.cpp|przykładowego kodu}})+//Zadanie 1.// Napisz ​własną wersję programu ''​netcat''​ wspierającą tylko klienta ​TCP – program, który nawiąże połączenie TCP pod wskazany adres, następnie dane przychodzące na standardowe wejście będzie wysyłać przez to połączenie,​ a dane które przyszły z sieci wypisze na standardowe wyjście.\\ (Możesz skorzystać z {{:​sk2:​tcp_client_template.cpp|przykładowego kodu}})
  
-//Zadanie 2.// Czat – napisz serwer, który każdą wiadomość przekaże wszystkim połączonym klientom. \\+//Zadanie 2.// Czat – napisz serwer, który każdą otrzymaną wiadomość przekaże wszystkim połączonym klientom. \\
 (Możesz skorzystać z {{:​sk2:​tcp_server_template.cpp|przykładowego kodu}}) (Możesz skorzystać z {{:​sk2:​tcp_server_template.cpp|przykładowego kodu}})
 ====== Zdarzenia ====== ====== Zdarzenia ======
Linia 22: Linia 22:
 System Linux zawiera 3 podstawowe funkcje pozwalające na czekanie na przychodzące zdarzenia na deskryptorach plików: System Linux zawiera 3 podstawowe funkcje pozwalające na czekanie na przychodzące zdarzenia na deskryptorach plików:
   * ''​select''​ – "​klasyczna"​ funkcja, ma kilka dziwnych ograniczeń. Dostaje zbiór deskryptorów,​ oczekuje na zdarzenie, modyfikuje przekazany zbiór deskryptorów zostawiając tylko te na których można wykonać read/​write/​lub na których wystąpił wyjątek. (POSIX)   * ''​select''​ – "​klasyczna"​ funkcja, ma kilka dziwnych ograniczeń. Dostaje zbiór deskryptorów,​ oczekuje na zdarzenie, modyfikuje przekazany zbiór deskryptorów zostawiając tylko te na których można wykonać read/​write/​lub na których wystąpił wyjątek. (POSIX)
-  * ''​poll''​ – zbudowana podobnie jak ''​select'',​ ale m.inn. nie ma ograniczenia na ilość ​monitorowanych deskryptorów,​ można użyć ponownie struktury opisującej deskryptory etc. (POSIX)+  * ''​poll''​ – zbudowana podobnie jak ''​select'',​ ale m.inn. nie ma ograniczenia na numery ​monitorowanych deskryptorów((Patrz [[https://​man7.org/​linux/​man-pages/​man2/​select.2.html|man 2 select]].)), można użyć ponownie struktury opisującej deskryptory etc. (POSIX)
   * ''​epoll''​ – specyficzna dla Linuksa funkcja. Inny pomysł: program informuje jądro systemu na które deskryptory chce czekać, potem wywołuje funkcję czekającą na zdarzenie i dostaje deskryptory które są gotowe do pracy.   * ''​epoll''​ – specyficzna dla Linuksa funkcja. Inny pomysł: program informuje jądro systemu na które deskryptory chce czekać, potem wywołuje funkcję czekającą na zdarzenie i dostaje deskryptory które są gotowe do pracy.
  
 Dłuższe porównanie:​ http://​www.ulduzsoft.com/​2014/​01/​select-poll-epoll-practical-difference-for-system-architects/​ Dłuższe porównanie:​ http://​www.ulduzsoft.com/​2014/​01/​select-poll-epoll-practical-difference-for-system-architects/​
  
 +<​small>​Od kilku lat trwają prace nad mechanizmem ''​[[https://​en.wikipedia.org/​wiki/​Io_uring|io_uring]]'',​ pozwalającym na wykonywanie jednocześnie wielu operacji na plikach. Mechanizm pozwala na przekazanie do kernela żądań wykonania konkretnych operacji, oraz oferuje funkcję do czekania na wykonanie jakiejś ze zleconych wcześniej operacji.</​small>​
 ===== poll ===== ===== poll =====
-//Zadanie 3.// Napisz jednowątkowego klientaktóry nawiąże jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości w następujący sposób+ 
-  - dodaj plik nagłówkowy ''​poll.h''​ +Aby stworzyć program korzystający z funkcji ''​poll''​należy
-  - przygotuj ​tablicę struktur ''​pollfd''​ i wypełnij+  - dodać ​plik nagłówkowy ''​poll.h''​ 
-    * ''​.fd'' ​to deskryptor pliku do monitorowania +  - przygotować ​tablicę struktur ''​pollfd''​ i wypełnić
-    * ''​.events'' ​to zbiór monitorowanych zdarzeń ​– np. ''​POLLIN'',​ ''​POLLOUT''​ (reszta w ''​man poll''​) +    * ''​.fd'' ​– deskryptor pliku do monitorowania, 
-    * zostaw ​w spokoju ''​.revents''​ – tam pojawi się informacja o tym co wystąpiło +    * ''​.events'' ​– zbiór monitorowanych zdarzeń
-  - w pętli wywołuj funkcję ''​poll(…)''​. Uwaga: aby funkcja czekała w nieskończoność,​ ''​timeout''​ musi być ujemny +      *  ​''​POLLIN'' ​– funkcja ''​poll''​ ma się przerwaćjeśli można wywołać bez czekania ''​read()/​…''​ lub ''​acccept()''​ 
-  - sprawdzaj ​który deskryptor jest gotowy przeglądając pola ''​.revents''​+      *  ​''​POLLOUT'' ​– funkcja ''​poll''​ ma się przerwać, jeśli można wywołać bez czekania ''​write()/​…''​ \\ Uwaga: na możliwość wysłania danych czeka się wtedy kiedy ma się dane do wysłania \\ Uwaga: jeśli spróbujesz zapisać więcej bajtów niż jest miejsca w buforze nadawczym, ''​write''​ i tak się zablokuje<​html></​div></​li></​ul><​div class="​li"></​html> ​reszta ​zdarzeń (''​POLLHUP'',​ ''​POLLERR'',​ ''​POLLPRI'',​ …) opisana ​w ''​man poll'' ​<​html></​div><​!--</​html>​ 
 +    * <​html>​--><​div class="​li"></​html>​ 
 +    * zostawić ​w spokoju ''​.revents''​ – tam pojawi się informacja o tym co wystąpiło 
 +  - w pętli wywoływać funkcję ''​poll(…)''​. Uwaga: aby funkcja czekała w nieskończoność,​ ''​timeout''​ musi być ujemny 
 +  - sprawdzać ​który deskryptor jest gotowy przeglądając pola ''​.revents''​
  
 <code cpp> <code cpp>
-pollfd ​nacoczekac[5]{}                 // \  +pollfd ​σληζονε[ιλε];                       ─┐ 
-nacoczekac[0].fd=sock1                 // ​ > (2) +σληζονε[0].fd = σοκητ_α                   │ 
-nacoczekac[0].events=POLLIN; ​            // /+σληζονε[0].events = POLLIN; ​                ​├─ ​(2) 
 +σληζονε[1].fd = σοκητ_β; ​                   │ 
 +σληζονε[1].events = POLLIN; ​               ​─┘
 ... ...
  
-int gotowe ​= poll(nacoczekac5, -1);    // ​(3)+while(true) { 
 +    ​int γωτουε ​= poll(σληζονειλε, -1);   ═╾─ ​(3)
  
-for(pollfd ​opis : nacoczekac) {        ​// \  +    if(σληζονε[0].revents ​POLLIN){       ─┐ ​  
-    if(opis.revents ​== POLLIN) {         //  | +        read(σοκητ_α,​ ...                   │ 
-        read(opis.fd, ...                // ​  > (4) +        ...                                 │ 
-        ...                              // ​ | +    }                                       ​├─ (4) 
-    }                                    // ​ | +    if(σληζονε[1].revents ​POLLIN){ ​       │ 
-                                       // /+        read(σοκητ_β, ...                   │ 
 +        ...                                ​─┘ 
 +    } 
 +    ... 
 +}
 </​code>​ </​code>​
 +
 +//Zadanie 3.// Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący ''​poll''​.
 ===== epoll ===== ===== epoll =====
  
 Mechanizm działania ''​epoll''​ jest inny niż ''​select''​ czy ''​poll''​. Mechanizm działania ''​epoll''​ jest inny niż ''​select''​ czy ''​poll''​.
-  - Wpierw programista musi stworzyć interfejs do monitorowania plików funkcją <​html><​small></​html>''​epoll_create''​ lub<​html></​small></​html>​ ''​epoll_create1''​ – interface ​trafia do tablicy deskryptorów plików (analogicznie do ''​open()'',​ ''​pipe()''​ czy ''​socket()''​). Po skończonej pracy zamyka się go podobnie jak każdy plik – funkcją ''​close''​. +  - Wpierw programista musi stworzyć interfejs do monitorowania plików funkcją <​html><​small></​html>''​epoll_create''​ lub<​html></​small></​html>​ ''​epoll_create1''​ – interfejs ​trafia do tablicy deskryptorów plików (analogicznie do ''​open()'',​ ''​pipe()''​ czy ''​socket()''​). Po skończonej pracy zamyka się go podobnie jak każdy plik – funkcją ''​close''​. 
-  - Lista monitorowanych deskryptorów jest przechowywana w pamięci jądra systemu operacyjnego – aby ją uzupełnić,​ należy użyć funkcji ''​epoll_ctl''​. Pozwala ona powiązać deskryptor z listą zdarzeń na które oczekuje program oraz dowolnymi danymi ​mieszącymi się w unii ''​epoll_data_t''​.+  - Lista monitorowanych deskryptorów jest przechowywana w pamięci jądra systemu operacyjnego – aby ją uzupełnić,​ należy użyć funkcji ''​epoll_ctl''​. Pozwala ona powiązać deskryptor z listą zdarzeń na które oczekuje program oraz dowolnymi danymi ​mieszczącymi się w unii ''​epoll_data_t''​.
   - Funkcja czekająca na zdarzenia – ''​epoll_wait''​ – przyjmuje jako argumenty tylko deskryptor utworzony przez ''​epoll_create'',​ tablicę zdarzeń do wypełnienia i czas oczekiwania (-1 = nieskończoność). \\   - Funkcja czekająca na zdarzenia – ''​epoll_wait''​ – przyjmuje jako argumenty tylko deskryptor utworzony przez ''​epoll_create'',​ tablicę zdarzeń do wypełnienia i czas oczekiwania (-1 = nieskończoność). \\
   - ''​epoll_wait''​ nie przekazuje numerów gotowych deskryptorów – przekazuje tylko rodzaj zdarzenia i powiązane z nim wcześniej dane.   - ''​epoll_wait''​ nie przekazuje numerów gotowych deskryptorów – przekazuje tylko rodzaj zdarzenia i powiązane z nim wcześniej dane.
Linia 62: Linia 76:
  
 <code cpp> <code cpp>
-int sock socket(... +int εΦδ ​epoll_create1(0);                       ​═╾─ (1)         
-... +                                                                
 +epoll_event ωπις; ​                                ​─┐ ​              
 +ωπις.events = EPOLLIN; ​                            ​│ ​              
 +ωπις.data.u64 = 987654321; ​                        ​│ ​             
 +epoll_ctl(εΦδ,​ EPOLL_CTL_ADD,​ σοκητ_α,​ &​ωπις); ​    ​├─ (2) 
 +ωπις.data.u64 = 123456789; ​                        │ 
 +epoll_ctl(εΦδ,​ EPOLL_CTL_ADD,​ σοκητ_β,​ &​ωπις); ​    │ 
 +...                                               ​─┘ ​              
 +// Poza .u64 w unii są też .fd i .ptr 
 +                                                                     
 +while(true) {                                                        
 +    int γωτουε = epoll_wait(εΦδ,​ &​ωπις,​ 1, -1);   ​═╾─ (3)           
 +                                                                     
 +    if(ωπις.events & EPOLLIN && ​                  ​─┐ ​                    
 +       ​ωπις.data.u64 == 987654321 ){               ​│ ​                  
 +       ​read(σοκητ_α,​ ...                           ​│ ​                  
 +    }                                              ├─ (4) 
 +    if(ωπις.events & EPOLLIN && ​                   │ 
 +       ​ωπις.data.u64 == 123456789 ){               │ 
 +       ​read(σοκητ_β,​ ...                          ─┘ 
 +    } 
 +    ... 
 +
 +</​code>​
  
-int fd  ​epoll_create1(0)                             // (1)+++++Przykład użycia pola ''​.data.ptr''​| 
 +<code cpp> 
 +struct Handler { 
 +    virtual void handleEvent(uint32_t event) ​= 0; 
 +};
  
-epoll_event event; ​                                      // \  +struct Client : public Handler { 
-  ​event.events = EPOLLIN                               // ​ | +    ​Client(int fd)
-  ​event.data.fd = sock                                 // ​  > ​(2+    ​int ​fd; 
-//event.data.u64 = 0xdeadbeef ​                           //  | +    virtual void handleEvent(uint32_t eventoverride { 
-//event.data.ptr = funkcja                             // /+        if(event & EPOLLIN){ 
 +            read(fd, ... 
 +        } 
 +        ​..
 +    } 
 +    ... 
 +};
  
-epoll_ctl(fd,​ EPOLL_CTL_ADD,​ sock, &event)             // \  +std::​list<​Client>​ clients
-...                                                      //  >  (2) +int epollfd;
-epoll_ctl(fd,​ EPOLL_CTL_ADD,​ othersock, &event)        // /+
  
- +void newClientArrived(){ 
-int resultCount ​epoll_wait(fd, &event, 1, -1);         ​// (3+   ​int sock accept(... 
- +   ​clients.emplace_back(sock);​ 
-if( event.events == EPOLLIN && event.data.fd == sock ){  // \  +   ​epoll_event ee {EPOLLIN{.ptr=&clients.back()}}; // referencje do elementów listy są ważne 
-    ​read(sock, ​...                                       //  > (4+   ​epoll_ctl(epollfd, EPOLL_CTL_ADD,​ sock, &ee);     // do usunięcia wskazywanego elementu 
-    ...                                                  // /+   ​... 
 +
 +    
 +int main(){ 
 +   ​... 
 +   ​epoll_event ee; 
 +   while(epoll_wait(epollfd,​ &ee, 1, -1)
 +     ((Handler*)ee.data.ptr)->​handleEvent(ee.events);
 } }
 </​code>​ </​code>​
 +++++
  
-//Zadanie 4.// Napisz jednowątkowego klientaktóry nawiąże jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości używając mechanizmu epoll +//Zadanie 4.// Powtórz Zadanie 1ale tym razem jako jednowątkowy ​program wykorzystujący mechanizm ​epoll.
- +
-//Zadanie 5.// Napisz ​jednowątkowy ​serwer czatu używając ''​poll''​ lub ''​epoll''​.\\ (Możesz skorzystać z {{:​sk2:​tcp_server_template.cpp|przykładowego kodu}})+
  
 +//Zadanie 5.// Napisz jednowątkowy serwer czatu używając ''​poll''​ lub ''​epoll_wait''​.
sk2/sockets_concurrency.1540761892.txt.gz · ostatnio zmienione: 2018/10/28 22:24 przez jkonczak