Простой эхо-сервер
Обслуживайте множество клиентов одновременно.
«Простой эхо-сервер» — бесплатный урок C Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C Academy содержит 4 уроков всего.
Что мы создаём
Эхо-сервер принимает TCP-соединения и отправляет обратно все байты, которые присылает клиент. Это «Hello, world!» сетевого программирования.
Мы создадим его на основе epoll, чтобы один поток одновременно обслуживал множество клиентов. Компоненты таковы: сокет, принимающий соединения, цикл epoll и обработка каждого клиента по схеме «прочитать, затем записать».
Создание сокета, принимающего соединения
Начните с socket() для конечной точки IPv4 TCP. Тройка параметров такова: AF_INET, SOCK_STREAM и протокол 0.
Функция возвращает файловый дескриптор, который мы привяжем к адресу, переведём в неблокирующий режим и в итоге зарегистрируем в epoll. Проверяйте значение -1 после каждого системного вызова.
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) { perror("socket"); exit(1); }Повторное использование адреса
Перед привязкой установите SO_REUSEADDR с помощью setsockopt(). Это позволит серверу перезапуститься и снова привязаться к тому же порту, не дожидаясь завершения состояния TIME_WAIT.
Без этого быстрый перезапуск часто завершается ошибкой EADDRINUSE, что особенно неприятно во время разработки.
int yes = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);Привязка к порту
Заполните struct sockaddr_in, указав семейство, порт и адрес. Для порта используйте htons(), а для прослушивания всех интерфейсов — INADDR_ANY.
Затем вызовите bind(), чтобы привязать сокет к этому адресу. htons преобразует порядок байтов хоста в сетевой порядок байтов, что необходимо.
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);Прослушивание и переход в неблокирующий режим
Вызовите listen(), чтобы перевести сокет в пассивный режим и задать очередь ожидающих соединений. Значение SOMAXCONN запрашивает максимальный размер, поддерживаемый системой.
Затем переведите сокет в неблокирующий режим, чтобы accept() никогда не приостанавливал цикл обработки событий. Теперь сокет готов к регистрации в epoll.
listen(listen_fd, SOMAXCONN);
set_nonblocking(listen_fd);Регистрация сокета, принимающего соединения
Создайте экземпляр epoll и добавьте сокет, принимающий соединения, с флагом EPOLLIN. Событие готовности к чтению здесь означает, что ожидает новый клиент.
Мы сохраняем дескриптор в data.fd, чтобы распознать этот сокет внутри цикла.
int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = listen_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);Приём клиента
Когда сокет, принимающий соединения, готов к чтению, выполняйте accept() в цикле до получения EAGAIN. Каждый новый клиентский сокет переводится в неблокирующий режим и регистрируется для EPOLLIN.
С этого момента epoll будет сообщать нам, когда клиент отправит данные, которые можно вернуть ему обратно.
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);
}Логика эхо-сервера
Для готового клиентского сокета выполните recv() в буфер. Если получены байты, сразу отправьте те же байты обратно с помощью send().
Это простая схема «прочитать, затем записать» и есть эхо-ответ. В рабочем приложении следовало бы буферизовать неотправленные байты, но в демонстрационном примере мы предполагаем, что небольшой ответ помещается в буфер отправки.
char buf[4096];
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r > 0) {
send(fd, buf, r, 0);
}Обнаружение отключения
Возвращаемое recv() значение 0 означает, что клиент корректно завершил соединение. Отрицательное значение с любым кодом, кроме EAGAIN, означает настоящую ошибку.
В обоих случаях удалите дескриптор из epoll и вызовите для него close(), чтобы освободить ресурсы и прекратить получение дальнейших событий.
if (r == 0 || (r < 0 && errno != EAGAIN)) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}Полный цикл
Объедините компоненты: ожидайте события, определяйте, принадлежит ли дескриптор сокету, принимающему соединения, или клиенту, и выполняйте соответствующую обработку.
Этот компактный цикл представляет собой полностью работоспособный параллельный эхо-сервер. Один поток, множество клиентов, отдельные потоки для соединений не нужны.
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);
}
}Тестирование и дальнейшие шаги
Скомпилируйте программу в Linux и подключитесь с помощью nc localhost 8080: всё, что Вы введёте, вернётся обратно. Откройте несколько терминалов, чтобы убедиться в параллельной обработке.
Чтобы сделать сервер надёжнее, буферизуйте частично отправленные данные, обрабатывайте EPOLLOUT, переключитесь на EPOLLET с полным чтением данных и обрабатывайте EPOLLRDHUP при частичном закрытии соединения.
Быстрая проверка
Проверьте, насколько хорошо Вы поняли работу эхо-сервера.
Итоги
Вы создали эхо-сервер на основе epoll: создали сокет, установили SO_REUSEADDR, привязали его, перевели в режим прослушивания и сделали неблокирующим. Зарегистрировали серверный сокет, принимали клиентов в цикле и отправляли им обратно полученные данные с помощью recv()/send().
Обрабатывайте возврат 0 как отключение и корректно закрывайте соединение. На основе этого каркаса можно создать высокопроизводительный однопоточный сетевой сервер.
Изучай C с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 39
- Уроки
- 144
Часто задаваемые вопросы
Урок «Простой эхо-сервер» бесплатный?
Да — полный текст урока «Простой эхо-сервер» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C Academy, подпишись на CoddyKit PRO. Курс C Academy содержит 4 уроков всего.
Чему я научусь в уроке «Простой эхо-сервер»?
Обслуживайте множество клиентов одновременно. Ты практикуешь C Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C Academy?
Предыдущий опыт не требуется. C Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Простой эхо-сервер»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C Academy?
Да. Каждый урок C Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.