Prosty serwer echo
Obsłuży Pan/Pani wielu klientów jednocześnie.
Prosty serwer echo to bezpłatna lekcja C Academy na CoddyKit. To lekcja 4 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.
Co zbudujemy
Serwer echo akceptuje połączenia TCP i odsyła wszystkie bajty wysłane przez klienta. To sieciowy odpowiednik programu „hello world”.
Zbudujemy go na bazie epoll, aby pojedynczy wątek mógł jednocześnie obsługiwać wielu klientów. Potrzebne elementy to: gniazdo nasłuchujące, pętla epoll oraz obsługa odczytu, a następnie zapisu dla każdego klienta.
Tworzenie gniazda nasłuchującego
Należy rozpocząć od socket() dla punktu końcowego IPv4 TCP. Trójka argumentów to AF_INET, SOCK_STREAM i protokół 0.
Funkcja zwraca deskryptor pliku, który powiążemy z adresem, ustawimy w tryb nieblokujący, a następnie zarejestrujemy w epoll. Każde wywołanie systemowe należy sprawdzać pod kątem wartości -1.
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) { perror("socket"); exit(1); }Ponowne użycie adresu
Przed powiązaniem gniazda z adresem należy ustawić SO_REUSEADDR za pomocą setsockopt(). Pozwala to serwerowi uruchomić się ponownie i ponownie użyć tego samego portu bez oczekiwania na zakończenie stanu TIME_WAIT.
Bez tej opcji szybkie ponowne uruchomienie często kończy się błędem EADDRINUSE, co podczas programowania bywa frustrującą niespodzianką.
int yes = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);Powiązanie z portem
Należy wypełnić struct sockaddr_in, podając rodzinę adresów, port i adres. Do portu należy użyć htons(), a wartości INADDR_ANY, aby nasłuchiwać na wszystkich interfejsach.
Następnie należy wywołać bind(), aby przypisać gniazdo do tego adresu. htons konwertuje kolejność bajtów hosta na kolejność bajtów sieciową, która jest wymagana.
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);
bind(listen_fd, (struct sockaddr*)&addr, sizeof addr);Nasłuchiwanie i tryb nieblokujący
Należy wywołać listen(), aby oznaczyć gniazdo jako pasywne i ustawić kolejkę oczekujących połączeń. SOMAXCONN żąda użycia maksymalnej wartości systemowej.
Następnie należy przełączyć gniazdo w tryb nieblokujący, aby accept() nigdy nie zatrzymywało pętli zdarzeń. Teraz gniazdo jest gotowe do zarejestrowania w epoll.
listen(listen_fd, SOMAXCONN);
set_nonblocking(listen_fd);Rejestrowanie gniazda nasłuchującego
Należy utworzyć instancję epoll i dodać gniazdo nasłuchujące z flagą EPOLLIN. Zdarzenie gotowości do odczytu oznacza tutaj, że na zaakceptowanie czeka nowy klient.
Przechowujemy deskryptor w data.fd, aby móc rozpoznać gniazdo nasłuchujące wewnątrz pętli.
int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = listen_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);Akceptowanie klienta
Gdy gniazdo nasłuchujące jest gotowe do odczytu, należy wywoływać accept() w pętli aż do uzyskania EAGAIN. Każde nowe gniazdo klienta należy ustawić w tryb nieblokujący i zarejestrować z flagą EPOLLIN.
Od tej chwili epoll będzie informować, gdy klient wyśle dane, które można odesłać.
int c = accept(listen_fd, NULL, NULL);
if (c != -1) {
set_nonblocking(c);
struct epoll_event cev = { .events = EPOLLIN, .data.fd = c };
epoll_ctl(epfd, EPOLL_CTL_ADD, c, &cev);
}Logika serwera echo
Dla gotowego klienta należy wykonać recv() do bufora. Jeśli zostaną odebrane bajty, należy odesłać te same bajty bezpośrednio za pomocą send().
Ten prosty odczyt, a następnie zapis, stanowi działanie serwera echo. W środowisku produkcyjnym należałoby buforować niewysłane bajty, ale na potrzeby demonstracji zakładamy, że mała odpowiedź mieści się w buforze wysyłania.
char buf[4096];
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r > 0) {
send(fd, buf, r, 0);
}Wykrywanie rozłączenia
Wartość 0 zwrócona przez recv() oznacza, że klient wykonał prawidłowe zamknięcie połączenia. Ujemna wartość zwrotna z czymkolwiek innym niż EAGAIN oznacza rzeczywisty błąd.
W obu przypadkach należy usunąć deskryptor z epoll i wywołać na nim close(), aby zwolnić zasoby i zatrzymać dalsze zgłaszanie zdarzeń.
if (r == 0 || (r < 0 && errno != EAGAIN)) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}Pełna pętla
Należy połączyć wszystkie elementy: oczekiwać na zdarzenia, rozgałęziać działanie zależnie od tego, czy deskryptor należy do gniazda nasłuchującego, czy do klienta, a następnie odpowiednio reagować.
Ta zwięzła pętla jest w pełni funkcjonalnym współbieżnym serwerem echo. Jeden wątek, wielu klientów, bez osobnych wątków dla poszczególnych połączeń.
for (;;) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == listen_fd) accept_clients();
else echo_or_close(events[i].data.fd);
}
}Testowanie i dalsze kroki
Należy skompilować program w systemie Linux i połączyć się za pomocą nc localhost 8080; wszystko, co Państwo wpiszą, zostanie odesłane. Warto otworzyć kilka terminali, aby sprawdzić współbieżność.
Aby zwiększyć odporność programu: buforuj częściowo wysłane dane, obsłuż EPOLLOUT, przełącz się na EPOLLET i całkowicie opróżniaj bufory, a także obsłuż EPOLLRDHUP w przypadku częściowego zamknięcia połączenia.
Szybkie sprawdzenie
Sprawdź swoje zrozumienie działania serwera echo.
Podsumowanie
Zbudowali Państwo serwer echo oparty na epoll: utworzenie, ustawienie SO_REUSEADDR, powiązanie, nasłuchiwanie i przejście w tryb nieblokujący. Należy zarejestrować gniazdo nasłuchujące, akceptować klientów w pętli oraz odsyłać dane za pomocą recv()/send().
Wartość 0 należy traktować jako rozłączenie i poprawnie zamykać połączenie. Na bazie tego szkieletu mogą Państwo rozbudować wydajny, jednowątkowy serwer sieciowy.
Często zadawane pytania
Czy lekcja „Prosty serwer echo” jest bezpłatna?
Tak — pełny tekst „Prosty serwer echo” 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 „Prosty serwer echo”?
Obsłuży Pan/Pani wielu klientów jednocześnie. Ć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 4 z 4.
Ile czasu zajmuje lekcja „Prosty serwer echo”?
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
- Blokujące a nieblokujące I/O
- Konfiguracja epoll
- Pętla zdarzeń
- Prosty serwer echo