Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:sockets_concurrency

To jest stara wersja strony!


Obsługa wielu strumieni naraz

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. 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.

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:

  • Iteracyjnie – kiedy współbieżność jest zbędna.
  • Pętla zdarzeń (event loop, [1], [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.

Wiele wątków

Wątki w C++11

Zadanie 1. Napisz klienta, który nawiąże jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości.
(Możesz skorzystać z przykładowego kodu)

Zadanie 2. Czat – napisz serwer, który każdą wiadomość przekaże wszystkim połączonym klientom.
(Możesz skorzystać z przykładowego kodu)

Zdarzenia

Funkcje biblioteczne

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)
  • 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)
  • 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/

poll

Zadanie 3. Napisz jednowątkowego klienta, który nawiąże jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości w następujący sposób:

  1. dodaj plik nagłówkowy poll.h
  2. przygotuj tablicę struktur pollfd i wypełnij:
    • .fd to deskryptor pliku do monitorowania
    • .events to zbiór monitorowanych zdarzeń – np. POLLIN, POLLOUT (reszta w man poll)
    • zostaw w spokoju .revents – tam pojawi się informacja o tym co wystąpiło
  3. w pętli wywołuj funkcję poll(…). Uwaga: aby funkcja czekała w nieskończoność, timeout musi być ujemny
  4. sprawdzaj który deskryptor jest gotowy przeglądając pola .revents
pollfd nacoczekac[5]{};                  // \ 
nacoczekac[0].fd=sock1;                  //  > (2)
nacoczekac[0].events=POLLIN;             // /
...
 
int gotowe = poll(nacoczekac, 5, -1);    // (3)
 
for(pollfd & opis : nacoczekac) {        // \ 
    if(opis.revents == POLLIN) {         //  |
        read(opis.fd, ...                //   > (4)
        ...                              //  |
    }                                    //  |
}                                        // /

epoll

Mechanizm działania epoll jest inny niż select czy poll.

  1. Wpierw programista musi stworzyć interfejs do monitorowania plików funkcją epoll_create lub 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.
  2. 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.
  3. 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ść).
  4. 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>

int sock = socket(...
... 
 
int fd =  epoll_create1(0);                              // (1)
 
epoll_event event;                                       // \ 
  event.events = EPOLLIN;                                //  |
  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);         // /
 
 
int resultCount = epoll_wait(fd, &event, 1, -1);         // (3)
 
if( event.events == EPOLLIN && event.data.fd == sock ){  // \ 
    read(sock, ...                                       //  > (4)
    ...                                                  // /
}

Przykład użycia pola .data.ptr

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)();

Zadanie 4. Napisz jednowątkowego klienta, który nawiąże jedno połączenie TCP i pozwoli wymieniać w obie strony wiadomości używając mechanizmu epoll

Zadanie 5. Napisz jednowątkowy serwer czatu używając poll lub epoll.

sk2/sockets_concurrency.1540764011.txt.gz · ostatnio zmienione: 2018/10/28 23:00 przez jkonczak