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 [2020/10/27 11:04]
jkonczak [Obsługa wielu strumieni naraz]
sk2:sockets_concurrency [2023/11/09 12:30] (aktualna)
jkonczak [Funkcje biblioteczne]
Linia 6: Linia 6:
 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: 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 ======
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.// Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący ''​poll''​. 
  
 Aby stworzyć program korzystający z funkcji ''​poll'',​ należy: Aby stworzyć program korzystający z funkcji ''​poll'',​ należy:
Linia 36: Linia 36:
     * ''​.events''​ – zbiór monitorowanych zdarzeń:     * ''​.events''​ – zbiór monitorowanych zdarzeń:
       *  ''​POLLIN''​ – funkcja ''​poll''​ ma się przerwać, jeśli można wywołać bez czekania ''​read()/​…''​ lub ''​acccept()''​       *  ''​POLLIN''​ – funkcja ''​poll''​ ma się przerwać, jeśli można wywołać bez czekania ''​read()/​…''​ lub ''​acccept()''​
-      *  ''​POLLOUT''​ – funkcja ''​poll''​ ma się przerwać, jeśli można wywołać bez czekania ''​write()/​…''​ \\ <​html><​small></​html>​Uwaga: jeśli spróbujesz zapisać więcej bajtów niż jest miejsca w buforze nadawczym, ''​write''​ i tak się zablokuje<​html></​small></​div></​li></​ul><​div class="​li"></​html>​ reszta zdarzeń (''​POLLHUP'',​ ''​POLLERR'',​ ''​POLLPRI'',​ …) opisana w ''​man poll''​ <​html></​div><​!--</​html>​+      *  ''​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>​     * <​html>​--><​div class="​li"></​html>​
     * zostawić w spokoju ''​.revents''​ – tam pojawi się informacja o tym co wystąpiło     * zostawić w spokoju ''​.revents''​ – tam pojawi się informacja o tym co wystąpiło
Linia 43: Linia 43:
  
 <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(σοκητ_α, ​...                   │ 
-        if(opis.fd == sock1) {           // ​ | +        ...                                 │ 
-            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 =====
  
Linia 68: 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
-epoll_event event; ​                                      // \. +struct Handler { 
-  event.events = EPOLLIN; ​                               //  ​+    ​virtual void handleEvent(uint32_t ​event) ​= 0
-  ​event.data.fd = sock;                                  //   (2) +};
-//​event.data.u64 = 0xdeadbeef ​                           //  | +
-//​event.data.ptr = funkcja; ​                             // / +
- +
-epoll_ctl(fd, EPOLL_CTL_ADD,​ sock, &event); ​             // \. +
-...                                                      //  >  (2) +
-epoll_ctl(fd,​ EPOLL_CTL_ADD,​ othersock, &event)        // /+
  
 +struct Client : public Handler {
 +    Client(int fd);
 +    int fd;
 +    virtual void handleEvent(uint32_t event) override {
 +        if(event & EPOLLIN){
 +            read(fd, ...
 +        }
 +        ...
 +    }
 +    ...
 +};
  
-int resultCount = epoll_wait(fd,​ &event, 1, -1)        // (3)+std::​list<​Client>​ clients; 
 +int epollfd;
  
-ifevent.events & EPOLLIN && event.data.fd == sock )  // \.  +void newClientArrived(){ 
-    read(sock, ...                                       //  > (4+   int sock = accept(... 
-    ...                                                  // /+   ​clients.emplace_back(sock)
 +   ​epoll_event ee {EPOLLIN, {.ptr=&​clients.back()}}; ​// referencje do elementów listy są ważne 
 +   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>​
-<​html><​small></​html>​Przykład użycia pola ''​.data.ptr''​ +++++
-<code cpp> +
-std::​function<​void()>​ readFromSock = ... // np. '​mySockObject;'​ czy '​std::​bind(myReadFunc,​ sock);'​ +
-epoll_event ee {}; +
-ee.events = EPOLLIN; +
-ee.data.ptr = &​readFromSock;​ +
-epoll_ctl(epollfd,​ EPOLL_CTL_ADD,​ sock, &ee); +
-... +
-while(epoll_wait(epollfd,​ &ee, 1, -1)) +
-    (*(std::​function<​void()>​*)ee.data.ptr)();​ +
-</​code>​ +
-<​html></​small></​html>​+
  
 //Zadanie 4.// Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący mechanizm epoll. //Zadanie 4.// Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący mechanizm epoll.
  
 //Zadanie 5.// Napisz jednowątkowy serwer czatu używając ''​poll''​ lub ''​epoll_wait''​. //Zadanie 5.// Napisz jednowątkowy serwer czatu używając ''​poll''​ lub ''​epoll_wait''​.
sk2/sockets_concurrency.1603793079.txt.gz · ostatnio zmienione: 2020/10/27 11:04 przez jkonczak