Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_concurrency [2019/11/12 08:29] jkonczak [epoll] |
sk2:sockets_concurrency [2024/11/12 11:52] (aktualna) jkonczak |
||
---|---|---|---|
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.#~~ 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 | ||
+ | równocześnie dane przychodzące z sieci będzie wypisywać na standardowe wyjście. | ||
+ | \\ | ||
+ | (Możesz skorzystać z {{:sk2:tcp_client_template.cpp|przykładowego kodu klienta}}) | ||
- | //Zadanie 2.// Czat – napisz serwer, który każdą otrzymaną wiadomość przekaże wszystkim połączonym klientom. \\ | + | ~~Zadanie.#~~ Czat – napisz serwer, który każdą otrzymaną wiadomość przekaże |
- | (Możesz skorzystać z {{:sk2:tcp_server_template.cpp|przykładowego kodu}}) | + | wszystkim połączonym klientom. |
+ | \\ | ||
+ | (Możesz skorzystać z {{:sk2:tcp_server_template.cpp|przykładowego kodu serwera}}) | ||
====== Zdarzenia ====== | ====== Zdarzenia ====== | ||
Linia 22: | Linia 30: | ||
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 informacje które zarzenia wystąpiły. |
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:<html><div style="margin-top:-1.4em"></div></html> |
- | - dodać plik nagłówkowy ''poll.h'' | + | |
- przygotować tablicę struktur ''pollfd'' i wypełnić: | - przygotować tablicę struktur ''pollfd'' i wypełnić: | ||
* ''.fd'' – deskryptor pliku do monitorowania, | * ''.fd'' – deskryptor pliku do monitorowania, | ||
* ''.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//: próba zapisania większej liczby bajtów niż jest wolnego miejsca w buforze nadawczym i tak zablokuje ''write''<html></div></li></ul><div class="li"></html>//uwaga//: jeśli na gnieździe wystąpił błąd, to ''read()/write()/…'' też można wywołać bez czekania \\ 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 | ||
- | - w pętli wywoływać funkcję ''poll(…)''. Uwaga: aby funkcja czekała w nieskończoność, ''timeout'' musi być ujemny | + | - w pętli wywoływać funkcję ''poll(…)''. \\ <small>ostatni argument funkcji ''poll'' to maksymalny czas oczekiwania; aby funkcja czekała bez limitu, należy podać tam dowolną ujemną wartość</small> |
- sprawdzać który deskryptor jest gotowy przeglądając pola ''.revents'' | - sprawdzać który deskryptor jest gotowy przeglądając pola ''.revents'' | ||
- | <code cpp> | + | Funkcja ''poll'', struktura ''pollfd'' i stałe ''POLL…'' są w pliku nagłówkowym ''#include <poll.h>'' |
- | pollfd nacoczekac[5]{}; // \. | + | |
- | nacoczekac[0].fd=sock1; // > (2) | + | |
- | nacoczekac[0].events=POLLIN; // / | + | |
- | ... | + | |
- | int gotowe = poll(nacoczekac, 5, -1); // (3) | + | <html> |
+ | <pre class="code cpp" style="user-select: none; line-height: 100%"> | ||
+ | <span title="Tablica struktur 'pollfd' opisująca na których plikach i na co czekać" style="background-color:#fb02">pollfd pfds<span class="br0">[</span>COUNT<span class="br0">]</span><span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span></span> <span style="color:grey">─┐</span> | ||
+ | <span title="poll() może monitorować dowolne pliki, np. standardowe wejście" style="background-color:#f002">pfds<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">fd</span> <span class="sy1">=</span> STDIN_FILENO<span class="sy4">;</span></span> pfds<span class="br0">[</span><span class="nu0">2</span><span class="br0">]</span><span title="'fd' wskazuje na którm pliku czekać na zdarzena, a 'events' określa na które zdarzenia na wskazanym pliku" style="background-color:#0f02">.<span class="me1">fd</span> <span class="sy1">=</span></span> cliSock1<span class="sy4">;</span> <span style="color:grey">│</span> | ||
+ | <span title="poll() może monitorować dowolne pliki, np. standardowe wejście" style="background-color:#f002">pfds<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">events</span> <span class="sy1">=</span> POLLIN<span class="sy4">;</span></span> pfds<span class="br0">[</span><span class="nu0">2</span><span class="br0">]</span><span title="'fd' wskazuje na którm pliku czekać na zdarzena, a 'events' określa na które zdarzenia na wskazanym pliku" style="background-color:#0f02">.<span class="me1">events</span> <span class="sy1">=</span></span> POLLIN<span class="sy4">;</span> <span style="color:grey">├─ (1)</span> | ||
+ | <span title="Na gnieździe nasłuchującym POLLIN oznacza możliwość wykonania accept() bez czekania" style="background-color:#00f2">pfds<span class="br0">[</span><span class="nu0">1</span><span class="br0">]</span>.<span class="me1">fd</span> <span class="sy1">=</span> serverSock<span class="sy4">;</span></span> pfds<span class="br0">[</span><span class="nu0">3</span><span class="br0">]</span>.<span class="me1">fd</span> <span class="sy1">=</span> cliSock2<span class="sy4">;</span> <span style="color:grey">│</span> | ||
+ | <span title="Na gnieździe nasłuchującym POLLIN oznacza możliwość wykonania accept() bez czekania" style="background-color:#00f2">pfds<span class="br0">[</span><span class="nu0">1</span><span class="br0">]</span>.<span class="me1">events</span> <span class="sy1">=</span> POLLIN<span class="sy4">;</span></span> pfds<span class="br0">[</span><span class="nu0">3</span><span class="br0">]</span>.<span class="me1">events</span> <span class="sy1">=</span> <span title="Jeśli zostanie podane POLLIN, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'read' (w buforze są dane lub wystąpił błąd na gnieździe)" style="background-color:#0ff4">POLLIN</span><span class="sy3">|</span><span title="Jeśli zostanie podane POLLOUT, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'write' (tzn. da się wysłać przynajmniej jeden bajt lub wystąpił błąd na gnieździe)" style="background-color:#ff04">POLLOUT</span><span class="sy4">;</span> <span style="color:grey">─┘</span> | ||
+ | ... ... | ||
+ | |||
+ | <span class="kw1">while</span><span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span><span class="br0">{</span> | ||
+ | <span class="kw4">int</span> ile_gotowych <span class="sy1">=</span> poll<span class="br0">(</span><span title="Tablica struktur 'pollfd' opisująca na których plikach i na co czekać" style="background-color:#fb02">pfds</span>, <span title="Wskazuje ile struktur pod wskaźnikiem 'pfds' ma odczytać funkcja 'poll'" style="background-color:#f082">liczbaStruktur</span>, <span title="Limit czasu oczekiwania na pierwsze zdarzenie: | ||
+ | nieujemna wartość: maksymalny czas czekania w milisekundach, | ||
+ | ujemna wartość: czekanie bez limitu czasowego" style="background-color:#0002"><span class="sy2">-</span><span class="nu0">1</span></span><span class="br0">)</span><span class="sy4">;</span> <span style="color:grey">═╾─ (2)</span> | ||
+ | |||
+ | <span title="poll() może monitorować dowolne pliki, np. standardowe wejście" style="background-color:#f002"><span class="kw1">if</span><span class="br0">(</span>pfds<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">revents</span> <span class="sy3">&</span> POLLIN<span class="br0">)</span> readingFromStdin<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span></span> <span style="color:grey">─┐</span> | ||
+ | <span class="kw1">if</span><span class="br0">(</span>pfds<span class="br0">[</span><span class="nu0">1</span><span class="br0">]</span><span title="poll() wypełnia pole .revents, wpisując tam identyfikatory wszystkich zdarzeń które wystąpiły na pliku" style="background-color:#0f42">.<span class="me1">revents</span> <span class="sy3">&</span></span> POLLIN<span class="br0">)</span> acceptingNewClient<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span style="color:grey">│</span> | ||
+ | <span class="kw1">for</span><span class="br0">(</span><span class="kw4">int</span> i <span class="sy1">=</span> <span class="nu0">2</span><span class="sy4">;</span> i <span class="sy1"><</span> liczbaStruktur<span class="sy4">;</span> <span class="sy2">++</span>i<span class="br0">)</span><span class="br0">{</span> <span style="color:grey">│</span> | ||
+ | <span title="Jeśli zostanie podane POLLIN, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'read' (w buforze są dane lub wystąpił błąd na gnieździe)" style="background-color:#0ff4"><span class="kw1">if</span><span class="br0">(</span>pfds<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">revents</span> <span class="sy3">&</span> POLLIN<span class="br0">)</span></span> <span style="color:grey">├─ (3)</span> | ||
+ | <span title="Jeśli zostanie podane POLLIN, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'read' (w buforze są dane lub wystąpił błąd na gnieździe)" style="background-color:#0ff4">readFromClient<span class="br0">(</span>pfds<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">fd</span><span class="br0">)</span><span class="sy4">;</span></span> <span style="color:grey">│</span> | ||
+ | <span title="Jeśli zostanie podane POLLOUT, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'write' (tzn. da się wysłać przynajmniej jeden bajt lub wystąpił błąd na gnieździe)" style="background-color:#ff04"><span class="kw1">if</span><span class="br0">(</span>pfds<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">revents</span> <span class="sy3">&</span> POLLOUT<span class="br0">)</span></span> <span style="color:grey">│</span> | ||
+ | <span title="Jeśli zostanie podane POLLOUT, to 'poll()' zakończy się kiedy tylko będzie dało się bez czekania wykonać 'write' (tzn. da się wysłać przynajmniej jeden bajt lub wystąpił błąd na gnieździe)" style="background-color:#ff04">writeToClient<span class="br0">(</span>pfds<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">fd</span><span class="br0">)</span><span class="sy4">;</span></span> <span style="color:grey">─┘</span> | ||
+ | <span class="br0">}</span> | ||
+ | <span class="br0">}</span> | ||
+ | </pre> | ||
+ | </html> | ||
+ | |||
+ | ~~Zadanie.#~~ Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący ''poll''. | ||
+ | \\ | ||
+ | (Możesz skorzystać z {{:sk2:tcp_client_template.cpp|przykładowego kodu klienta}}) | ||
- | for(pollfd & opis : nacoczekac) { // \. | ||
- | if(opis.revents & POLLIN) { // | | ||
- | if(opis.fd == sock1) { // | | ||
- | read(opis.fd, ... // > (4) | ||
- | ... // | | ||
- | }} // | | ||
- | } // / | ||
- | </code> | ||
===== epoll ===== | ===== epoll ===== | ||
Linia 65: | Linia 88: | ||
- 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. | ||
- | Plik nagłówkowy: ''#include <sys/epoll.h>'' | + | Funkcje ''epoll_…'', struktura ''epoll_event'' i stałe ''EPOLL…'' są w pliku nagłówkowym ''#include <sys/epoll.h>'' |
- | <code cpp> | + | <html> |
- | int sock = socket(... | + | <pre class="code cpp" style="user-select: none; line-height: 100%"> |
- | ... | + | <span title="Pomocnicze zmienne, struktury i funkcje" style="opacity: 0.75; background-color:#0001"><span class="kw4">struct</span> Client <span class="br0">{</span> |
+ | <span class="kw4">int</span> fd<span class="sy4">;</span> | ||
+ | <span class="kw4">char</span> <span class="sy2">*</span>partialDataReceived, <span class="sy2">*</span>dataQueuedToSend<span class="sy4">;</span> | ||
+ | <span class="br0">}</span> <span class="sy2">*</span>clients<span class="sy4">;</span> | ||
+ | <span class="kw4">void</span> receiveFromCli<span class="br0">(</span>Client <span class="sy2">*</span>c<span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="kw4">void</span> sendQueuedData<span class="br0">(</span>Client <span class="sy2">*</span>c<span class="br0">)</span><span class="sy4">;</span></span> | ||
- | int fd = epoll_create1(0); // (1) | + | ... |
- | epoll_event event; // \. | + | <span title="'epoll_create1' tworzy nowy plik do monitorowania zdarzeń na innych plikach" style="background-color:#ff02"><span class="kw4">int</span> epollDescr <span class="sy1">=</span> epoll_create1<span class="br0">(</span><span class="nu0">0</span><span class="br0">)</span><span class="sy4">;</span></span> ═╾─ (1) |
- | event.events = EPOLLIN; // | | + | |
- | event.data.fd = sock; // > (2) | + | |
- | //event.data.u64 = 0xdeadbeef // | | + | |
- | //event.data.ptr = funkcja; // / | + | |
- | epoll_ctl(fd, EPOLL_CTL_ADD, sock, &event); // \. | + | <span title="Struktura epoll_event jest używana: |
- | ... // > (2) | + | 1) w epoll_ctl jako argument wejściowy, do określenia na jakie zdarzenia czekać i powiązania z nimi danych |
- | epoll_ctl(fd, EPOLL_CTL_ADD, othersock, &event); // / | + | 2) w epoll_wait jako argument wyjściowy, określa jakie zdarzenia wystąpiły i przekazania powiązanych z nimi danych" style="background-color:#f802">epoll_event</span> ee<span class="br0">[</span><span class="nu0">2</span><span class="br0">]</span><span class="sy4">;</span> ─┐ |
+ | ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span title="'.events' określa które zdarzenia mają być raportowane" style="background-color:#0f02">.<span class="me1">events</span> <span class="sy1">=</span></span> EPOLLIN<span class="sy4">;</span> │ | ||
+ | ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span title="Jeśli wskazane zdarzenie wystąpi, to (skopiowana do pamięci systemu operacyjngo w momencie wywołania 'epoll_ctl') wartość unii '.data' zostanie z powrotem wpisana przez 'epoll_wait' do wynikowej struktury epoll_event | ||
+ | W tej unii do wybor są pola fd, u32, u64 i ptr, odpowiednio typów int, uint32_t, uint64_t i void*" style="background-color:#f002">.<span class="me1">data</span>.<span class="me1">u32</span> <span class="sy1">=</span></span> <span class="sy2">-</span><span class="nu0">1</span><span class="sy4">;</span> ├─ (2) | ||
+ | <span title="'epoll_ctl' zarządza listą zdarzeń które monitoruje wskazany plik epoll: | ||
+ | z flagą EPOLL_CTL_ADD dodaje nowy plik, | ||
+ | z flagą EPOLL_CTL_DEL przestaje monitorować wskazany plik, | ||
+ | z flagą EPOLL_CTL_MOD zmienia listę zdarzeń i powiązane dane dla już monitorowanego pliku" style="background-color:#f0f2">epoll_ctl</span><span class="br0">(</span>epollDescr, <span title="'epoll_ctl' zarządza listą zdarzeń które monitoruje wskazany plik epoll: | ||
+ | z flagą EPOLL_CTL_ADD dodaje nowy plik, | ||
+ | z flagą EPOLL_CTL_DEL przestaje monitorować wskazany plik, | ||
+ | z flagą EPOLL_CTL_MOD zmienia listę zdarzeń i powiązane dane dla już monitorowanego pliku" style="background-color:#f0f2">EPOLL_CTL_ADD</span>, STDIN_FILENO, <span class="sy3">&</span>ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span> <span class="sy1">=</span> <span class="sy2">-</span><span class="nu0">2</span><span class="sy4">;</span> │ | ||
+ | epoll_ctl<span class="br0">(</span><span title="Wybiera na którym z plików epoll będzie pracować ta funkcja" style="background-color:#0fa2">epollDescr</span>, EPOLL_CTL_ADD, <span title="Określa na którym pliku należy rozpocząć (lub zmienić/zakończyć) śledzenie zdarzeń" style="background-color:#0082">serverSock</span>, <span title="Wskazuje które zdarzenia mają być raportowane i jakie dane programista chce otrzymać, kiedy wskazane zdarzenie wystąpi" style="background-color:#a0f2"><span class="sy3">&</span>ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span class="br0"></span></span>)<span class="sy4">;</span> ─┘ | ||
+ | <span class="kw1">while</span> <span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span> <span class="br0">{</span> | ||
+ | <span title="'epoll_wait' zwraca ile pierwszych struktur z podanej listy wypełnił" style="background-color:#f402"><span class="kw4">int</span> ile_gotowych</span> <span class="sy1">=</span> <span title="'epoll_wait' wpisuje do podanej listy struktur epoll_event listę zdarzeń które wystąpiły na wcześniej zgłoszonych do monitorowania plikach (jeśli trzeba, czekając na pierwsze zdarzenie)" style="background-color:#0ff2">epoll_wait</span><span class="br0">(</span><span title="Wybiera na którym z plików epoll będzie pracować ta funkcja" style="background-color:#0fa2">epollDescr</span>, <span title="wskazuje że wyniki mają trafić do tablicy 'ee' w której jest miejsce na co najwyżej 2 elementy" style="background-color:#8f03">ee, <span class="nu0">2</span></span>, <span title="limit czasu oczekiwania (jak w poll)" style="background-color:#0001"><span class="sy2">-</span><span class="nu0">1</span></span><span class="br0">)</span><span class="sy4">;</span> ═╾─ (3) | ||
- | int resultCount = epoll_wait(fd, &event, 1, -1); // (3) | + | <span class="kw1">for</span> <span class="br0">(</span><span class="kw4">int</span> i <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span> i <span class="sy1"><</span> ile_gotowych<span class="sy4">;</span> <span class="sy2">++</span>i<span class="br0">)</span> <span class="br0">{</span> ─┐ |
+ | <span class="kw1">if</span> <span class="br0">(</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span> <span class="sy1">==</span> <span class="sy2">-</span><span class="nu0">1</span><span class="br0">)</span> │ | ||
+ | readingFromStdin<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | <span class="kw1">else</span> <span class="kw1">if</span> <span class="br0">(</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span> <span class="sy1">==</span> <span class="sy2">-</span><span class="nu0">2</span><span class="br0">)</span> <span class="br0">{</span> │ | ||
+ | <span class="kw4">int</span> cliFd <span class="sy1">=</span> accept<span class="br0">(</span>serverSock, <span class="nu0">0</span>, <span class="nu0">0</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | <span class="kw4">int</span> cliId <span class="sy1">=</span> getFreeClientId<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | clients<span class="br0">[</span>cliId<span class="br0">]</span> <span class="sy1">=</span> <span class="br0">{</span>.<span class="me1">fd</span> <span class="sy1">=</span> cliFd<span class="br0">}</span><span class="sy4">;</span> │ | ||
+ | ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">events</span> <span class="sy1">=</span> EPOLLIN<span class="sy4">;</span> ┐ │ | ||
+ | ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span> <span class="sy1">=</span> cliId<span class="sy4">;</span> ├(2)├─ (4) | ||
+ | epoll_ctl<span class="br0">(</span>epollDescr, EPOLL_CTL_ADD, cliFd, <span class="sy3">&</span>ee<span class="br0">[</span><span class="nu0">0</span><span class="br0">]</span><span class="br0">)</span><span class="sy4">;</span> ┘ │ | ||
+ | <span class="br0">}</span> <span title="W tym przykładzie programista używa wartości specjalne -2 i -1 dla standardowego wejścia i gniazda serwerowego, a pozostałe wartości pola .data.u32 to indeksy w tablicy klientów, co ułatwia odnalezienie danych powiązanych z klientem" style="background-color:#0ff4"><span class="kw1">else</span></span> <span class="br0">{</span> │ | ||
+ | <span class="kw1">if</span> <span class="br0">(</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">events</span> <span class="sy3">&</span> EPOLLIN<span class="br0">)</span> │ | ||
+ | receiveFromCli<span class="br0">(</span><span class="sy3">&</span>clients<span class="br0">[</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span><span class="br0">]</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | <span class="kw1">if</span> <span class="br0">(</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">events</span> <span class="sy3">&</span> EPOLLOUT<span class="br0">)</span> │ | ||
+ | sendQueuedData<span class="br0">(</span><span class="sy3">&</span>clients<span class="br0">[</span>ee<span class="br0">[</span>i<span class="br0">]</span>.<span class="me1">data</span>.<span class="me1">u32</span><span class="br0">]</span><span class="br0">)</span><span class="sy4">;</span> │ | ||
+ | <span class="br0">}</span> │ | ||
+ | <span class="br0">}</span> ─┘ | ||
+ | <span class="br0">}</span> | ||
+ | </pre> | ||
+ | </html> | ||
- | if( event.events & EPOLLIN && event.data.fd == sock ){ // \. | + | ++++Przykład użycia pola ''.data.ptr''| |
- | read(sock, ... // > (4) | + | <html> |
- | ... // / | + | <pre class="code cpp" style="user-select: none; line-height: 100%"> |
- | } | + | <span style="background-color:#0001" title="Każdy wskaźnik umieszczany w danych powiązanych ze zdarzeniem w epoll będzie obiektem z klasy która dziedziczy po Handler, więc będzie mieć metodę 'handle'"><span class="kw4">struct</span> Handler <span class="br0">{</span> |
- | </code> | + | <span class="kw2">virtual</span> <span class="kw4">void</span> handle<span class="br0">(</span>epoll_event <span class="sy3">&</span>ee<span class="br0">)</span> <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span> |
- | <html><small></html>Przykład użycia pola ''.data.ptr'' | + | <span style="opacity:0.66"><span class="kw2">virtual</span> ~Handler<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span></span> |
- | <code cpp> | + | <span class="br0">}</span><span class="sy4">;</span></span> |
- | std::function<void()> readFromSock = ... // np. 'mySockObject;' czy 'std::bind(myReadFunc, sock);' | + | |
- | epoll_event ee {}; | + | <span style="opacity:0.66"><span class="kw4">struct</span> Client<span class="sy4">;</span> |
- | ee.events = EPOLLIN; | + | std<span class="sy4">::</span><span class="me2">set</span><span class="sy1"><</span>Client <span class="sy2">*</span><span class="sy1">></span> clients<span class="sy4">;</span></span> |
- | ee.data.ptr = &readFromSock; | + | <span style="background-color:#8001" title="Klasa Client ma obsługiwać jednego klienta – czytać z jego gniazda i pisać do niego (w tym przechowywać koleję danych do wysłania i początki częściowo dostarczoncyh wiadomości), może też od razu zawierać logikę obsługi klienta"><span class="kw4">struct</span> Client</span> <span class="sy4">:</span> <span class="kw2">public</span> Handler <span class="br0">{</span> |
- | epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &ee); | + | <span style="opacity:0.66"><span class="kw4">char</span> <span class="sy2">*</span>partialDataReceived, <span class="sy2">*</span>dataQueuedToSend<span class="sy4">;</span> |
- | ... | + | <span class="kw4">int</span> epollDescr, fd<span class="sy4">;</span></span> |
- | while(epoll_wait(epollfd, &ee, 1, -1)) | + | <span style="background-color:#0081" title="W konstruktorze klasa Client automatycznie dodaje obsługiwane przez siebie gniazdo do monitorowania"> Client<span class="br0">(</span><span class="kw4">int</span> epollDescr, <span class="kw4">int</span> fd<span class="br0">)</span> <span class="sy4">:</span> epollDescr<span class="br0">(</span>epollDescr<span class="br0">)</span>, fd<span class="br0">(</span>fd<span class="br0">)</span> <span class="br0">{</span> |
- | (*(std::function<void()>*)ee.data.ptr)(); | + | epoll_event ee<span class="br0">{</span>.<span class="me1">events</span> <span class="sy1">=</span> EPOLLIN, .<span class="me1">data</span> <span class="sy1">=</span> <span class="br0">{</span>.<span class="me1">ptr</span> <span class="sy1">=</span> <span class="kw3">this</span><span class="br0">}</span><span class="br0">}</span><span class="sy4">;</span> |
- | </code> | + | epoll_ctl<span class="br0">(</span>epollDescr, EPOLL_CTL_ADD, fd, <span class="sy3">&</span>ee<span class="br0">)</span><span class="sy4">;</span> |
- | <html></small></html> | + | <span style="opacity:0.66">clients.<span class="me1">insert</span><span class="br0">(</span><span class="kw3">this</span><span class="br0">)</span><span class="sy4">;</span></span> |
+ | <span class="br0">}</span></span> | ||
+ | <span style="background-color:#0801" title="W destruktorze klasa Client zamyka gniazdo, co automatycznie usuwa gniazdo ze wszystkich epolli"> ~Client<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> | ||
+ | close<span class="br0">(</span>fd<span class="br0">)</span><span class="sy4">;</span> | ||
+ | ... | ||
+ | <span class="br0">}</span></span> | ||
+ | <span style="opacity:0.66"><span class="kw4">void</span> receive<span class="br0">(</span>epoll_event <span class="sy3">&</span>ee<span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="kw4">void</span> sendQueuedData<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span></span> | ||
+ | <span class="kw4">void</span> handle<span class="br0">(</span>epoll_event <span class="sy3">&</span>ee<span class="br0">)</span> <span class="br0">{</span> | ||
+ | <span style="background-color:#0001" title="Zwróć uwagę że poza odbieraniem danych funkcja 'receive' musi też obsłużyć błędy na gnieździe"> <span class="kw1">if</span> <span class="br0">(</span>ee.<span class="me1">events</span> <span class="sy3">&</span> EPOLLIN<span class="br0">)</span> | ||
+ | receive<span class="br0">(</span>ee<span class="br0">)</span><span class="sy4">;</span></span> | ||
+ | <span class="kw1">if</span> <span class="br0">(</span>ee.<span class="me1">events</span> <span class="sy3">&</span> EPOLLOUT<span class="br0">)</span> | ||
+ | sendQueuedData<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="br0">}</span> | ||
+ | ... | ||
+ | <span class="br0">}</span><span class="sy4">;</span> | ||
+ | <span class="kw4">struct</span> Server <span class="sy4">:</span> <span class="kw2">public</span> Handler <span class="br0">{</span> | ||
+ | <span style="opacity:0.66"><span class="kw4">int</span> epollDescr, serverSock<span class="sy4">;</span></span> | ||
+ | <span style="background-color:#0081" title="W konstruktorze klasa Serwer tworzy nowe nasłuchujące gniazdo i dodaje je do monitorowania we wskazanym epollu"> Server<span class="br0">(</span><span class="kw4">int</span> epollDescr, <span class="kw4">uint16_t</span> port<span class="br0">)</span> <span class="sy4">:</span> epollDescr<span class="br0">(</span>epollDescr<span class="br0">)</span> <span class="br0">{</span> | ||
+ | serverSock <span class="sy1">=</span> ... | ||
+ | ... | ||
+ | epoll_event ee<span class="br0">{</span>.<span class="me1">events</span> <span class="sy1">=</span> EPOLLIN, .<span class="me1">data</span> <span class="sy1">=</span> <span class="br0">{</span>.<span class="me1">ptr</span> <span class="sy1">=</span> <span class="kw3">this</span><span class="br0">}</span><span class="br0">}</span><span class="sy4">;</span> | ||
+ | epoll_ctl<span class="br0">(</span>epollDescr, EPOLL_CTL_ADD, serverSock, <span class="sy3">&</span>ee<span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="br0">}</span></span> | ||
+ | <span style="opacity:0.66">~Server<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span></span> | ||
+ | <span style="background-color:#8001" title="W obsłudze zdarzenia – żądanego POLLIN bądź błędu (którego obsługa tu jest pominięta) – przyjmowane jest nowe połączenie i tworzony obiekt z klasy Client, który sam dodaje obsługiwane przez siebie gniazdo do epolla"> <span class="kw4">void</span> handle<span class="br0">(</span>epoll_event <span class="sy3">&</span><span class="br0">)</span> <span class="br0">{</span> | ||
+ | <span class="kw4">int</span> cliFd <span class="sy1">=</span> accept<span class="br0">(</span>serverSock, <span class="nu0">0</span>, <span class="nu0">0</span><span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="kw3">new</span> Client<span class="br0">(</span>epollDescr, cliFd<span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="br0">}</span></span> | ||
+ | <span class="br0">}</span><span class="sy4">;</span> | ||
+ | |||
+ | <span style="opacity:0.66"><span class="kw4">int</span> main<span class="br0">()</span> <span class="br0">{</span></span> | ||
+ | ... | ||
+ | <span class="kw4">int</span> epollDescr <span class="sy1">=</span> epoll_create1<span class="br0">(</span><span class="nu0">0</span><span class="br0">)</span><span class="sy4">;</span> | ||
+ | Server srv<span class="br0">(</span>epollDescr, port<span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span class="kw1">while</span> <span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span> <span class="br0">{</span> | ||
+ | epoll_event ee<span class="sy4">;</span> | ||
+ | epoll_wait<span class="br0">(</span>epollDescr, <span class="sy3">&</span>ee, <span class="nu0">1</span>, <span class="sy2">-</span><span class="nu0">1</span><span class="br0">)</span><span class="sy4">;</span> | ||
+ | <span style="background-color:#0001" title="Wartość .data.ptr zawsze wskazuje na obiekt z klasy (dziedzicząej po) Handler, z metodą 'handle' do obsługi zdarzenia"><span class="br0">(</span><span class="br0">(</span>Handler <span class="sy2">*</span><span class="br0">)</span>ee.<span class="me1">data</span>.<span class="me1">ptr</span><span class="br0">)</span><span class="sy2">-</span><span class="sy1">></span>handle<span class="br0">(</span>ee<span class="br0">)</span><span class="sy4">;</span></span> | ||
+ | <span class="br0">}</span> | ||
+ | <span style="opacity:0.66"><span class="br0">}</span></span> | ||
+ | </pre> | ||
+ | </html> | ||
+ | ++++ | ||
- | //Zadanie 4.// Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący mechanizm epoll. | + | ~~Zadanie.#~~ Powtórz Zadanie 1, ale tym razem jako jednowątkowy program wykorzystujący mechanizm epoll. |
+ | \\ | ||
+ | (Możesz skorzystać z {{:sk2:tcp_client_template.cpp|przykładowego kodu klienta}}) | ||
- | //Zadanie 5.// Napisz jednowątkowy serwer czatu używając ''poll'' lub ''epoll_wait''. | + | ~~Zadanie.#~~ Napisz jednowątkowy serwer czatu używając ''poll'' lub ''epoll_wait''. |
+ | \\ | ||
+ | (Możesz skorzystać z {{:sk2:tcp_server_template.cpp|przykładowego kodu serwera}}) |