Asynchroniczne wejście/wyjście z epoll i kqueue
Obsługuj wiele połączeń za pomocą epoll w systemie Linux oraz kqueue w BSD lub macOS
Asynchroniczne wejście/wyjście z epoll i kqueue to bezpłatna lekcja C++ Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C++ Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C++ Academy zawiera 4 lekcji w sumie.
Dlaczego asynchroniczne wejście/wyjście?
Model z jednym wątkiem na połączenie nie skaluje się do tysięcy ani milionów klientów. Asynchroniczne wejście/wyjście pozwala jednemu wątkowi obsługiwać wiele gniazd, oczekując, aż któreś z nich będzie gotowe do odczytu lub zapisu.
Linux: epoll
epoll to skalowalne API zdarzeń systemu Linux. Dodaj deskryptory plików do instancji epoll i czekaj na zdarzenia dotyczące dowolnego z nich.
#include <sys/epoll.h>
int epfd = epoll_create1(0);
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);Oczekiwanie na zdarzenia
epoll_wait blokuje wykonanie do momentu pojawienia się zdarzeń, a następnie zwraca gotowe deskryptory.
epoll_event events[64];
int n = epoll_wait(epfd, events, 64, -1);
for (int i = 0; i < n; ++i) {
int fd = events[i].data.fd;
handle_io(fd);
}Wyzwalanie krawędziowe a poziomowe
Dostępne są dwa tryby:
- Wyzwalanie poziomowe (domyślne) — epoll nadal zgłasza zdarzenia, dopóki warunek jest spełniony
- Wyzwalanie krawędziowe (
EPOLLET) — zgłasza zdarzenie tylko przy zmianie stanu; za każdym razem trzeba całkowicie opróżnić gniazdo
macOS i BSD: kqueue
kqueue jest odpowiednikiem epoll w systemach BSD i jest również dostępne w macOS. Koncepcyjnie mechanizmy są podobne, ale korzystają z innego API.
#include <sys/event.h>
int kq = kqueue();
struct kevent ev;
EV_SET(&ev, sock, EVFILT_READ, EV_ADD, 0, 0, nullptr);
kevent(kq, &ev, 1, nullptr, 0, nullptr);Windows: IOCP
System Windows korzysta z portów zakończenia operacji wejścia/wyjścia (IOCP) — modelu opartego na zakończeniu operacji (powiadomienie po jej wykonaniu), a nie na gotowości, jak w epoll/kqueue.
Gniazda nieblokujące
Ustaw gniazda w tryb nieblokujący za pomocą fcntl(fd, F_SETFL, O_NONBLOCK). W przeciwnym razie odczyty i zapisy mogą zablokować całą pętlę zdarzeń.
Wzorzec pętli zdarzeń
Pętla działająca w nieskończoność:
- Czekaj na gotowe zdarzenia
- Dla każdego zdarzenia wykonuj operacje wejścia/wyjścia, dopóki nie doszłoby do blokowania
- Powtarzaj
Obsługa wielu klientów
Pojedynczy wątek epoll może obsługiwać dziesiątki tysięcy jednoczesnych połączeń — to problem C10K. Współczesne systemy obsługują nawet miliony połączeń.
Typowe pułapki
Zwróć uwagę na:
- zapomnienie o ustawieniu trybu nieblokującego
- użycie wyzwalania krawędziowego bez opróżniania gniazda
- nieusuwanie zamkniętych deskryptorów z zestawu epoll
- wycieki buforów dla poszczególnych połączeń
Abstrakcje wyższego poziomu
Samodzielne napisanie pętli epoll łatwo prowadzi do błędów. W kodzie produkcyjnym użyj libuv, libevent lub Boost.Asio — biblioteki te ukrywają różnice między platformami i udostępniają przetestowane pętle wejścia/wyjścia.
Asynchroniczne wejście/wyjście a wątki
Asynchroniczne wejście/wyjście lepiej się skaluje, ale jest trudniejsze w implementacji. W przypadku serwerów z kilkoma tysiącami połączeń model z jednym wątkiem na połączenie jest prostszy. Wybierz rozwiązanie na podstawie przewidywanego obciążenia.
Szybkie sprawdzenie
Czym różnią się tryby wyzwalania krawędziowego i poziomowego w epoll?
Podsumowanie
Asynchroniczne wejście/wyjście skaluje się do ogromnej liczby jednoczesnych połączeń, obsługując wiele gniazd w jednym wątku. Linux korzysta z epoll, BSD/macOS z kqueue, a Windows z IOCP. Ustaw gniazda w tryb nieblokujący. W kodzie produkcyjnym użyj biblioteki (Asio, libuv, libevent) zamiast ręcznie pisać pętlę zdarzeń.
Często zadawane pytania
Czy lekcja „Asynchroniczne wejście/wyjście z epoll i kqueue” jest bezpłatna?
Tak — pełny tekst „Asynchroniczne wejście/wyjście z epoll i kqueue” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C++ Academy, przejdź na CoddyKit PRO. Kurs C++ Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Asynchroniczne wejście/wyjście z epoll i kqueue”?
Obsługuj wiele połączeń za pomocą epoll w systemie Linux oraz kqueue w BSD lub macOS Ćwiczysz C++ Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C++ Academy?
Nie wymagamy żadnego doświadczenia. C++ Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Asynchroniczne wejście/wyjście z epoll i kqueue”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C++ Academy?
Tak. Każda lekcja C++ Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- API gniazd BSD w C++
- Budowa serwera echa TCP
- Asynchroniczne wejście/wyjście z epoll i kqueue
- Nowoczesne asynchroniczne wejście/wyjście z Boost Asio