Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_concurrency [2018/11/05 13:55] jkonczak [poll] |
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 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 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}}) | ||
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(nacoczekac, 5, -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 ===== | ||
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'' – 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''. | - 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 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; | ||
- | if( event.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''. |