Привет, Хабр!
Сегодня будем писать HTTP‑сервер на C. Если когда‑нибудь вас заставят написать сервер без libcurl, boost::asio или microhttpd, вы будете готовы.
Сокеты
Начнём с азов. Чтобы принимать соединения нужен сокет. Через который к нам будут подключаться браузеры, Postman-ы и другие.
Создадим TCP‑сервер:
#include <stdio.h> // printf, perror #include <stdlib.h> // exit #include <string.h> // memset, strlen #include <unistd.h> // close #include <sys/types.h> // типы данных для сокетов #include <sys/socket.h> // сами сокеты #include <netinet/in.h> // sockaddr_in #include <arpa/inet.h> // htons #define PORT 8080 // Будем слушать этот порт #define BACKLOG 10 // Очередь входящих соединений int main() { int server_fd; struct sockaddr_in server_addr; // 1. Создаём сокет: IPv4, TCP server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("Ошибка создания сокета"); exit(EXIT_FAILURE); } // 2. Привязываем сокет к адресу и порту server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Ошибка привязки"); exit(EXIT_FAILURE); } // 3. Переключаем в режим прослушивания if (listen(server_fd, BACKLOG) < 0) { perror("Ошибка listen()"); exit(EXIT_FAILURE); } printf("Сервер запущен на порту %d\n", PORT); close(server_fd); return 0; }
Создаём сокет socket(AF_INET, SOCK_STREAM, 0). Привязываем к порту: bind(), указываем INADDR_ANY, чтобы слушать все интерфейсы. Запускаем прослушку: listen(), ставим BACKLOG = 10, чтобы не дропать входящие соединения
Этот код пока только запускает сервер. Никаких клиентов, но зато уже можно увидеть терминале:
Сервер запущен на порту 8080
Клиент подключился. Что делать?
Теперь принимаем соединения. Дописываем accept() и читаем HTTP‑запрос:
int client_fd; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); while (1) { printf("\nОжидание нового соединения...\n"); // 4. Принимаем входящее соединение client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("Ошибка accept()"); continue; } printf("Подключился клиент: %s\n", inet_ntoa(client_addr.sin_addr)); // 5. Читаем HTTP-запрос char buffer[1024] = {0}; read(client_fd, buffer, sizeof(buffer) - 1); printf("Запрос:\n%s\n", buffer); close(client_fd); }
accept() создаёт новый сокет для клиента. Читаем read() первый килобайт запроса. Выводим его в консоль
Если запустить и подключиться через curl, то можно увидеть запрос:
curl -v http://localhost:8080/
На сервере появится что‑то типа:
Запрос: GET / HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.68.0 Accept: */*
Формируем HTTP-ответ
Теперь серверу надо ответить. Это делается через write().
Минимальный ответ:
char response[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Content-Length: 13\r\n" "\r\n" "Hello, Habr!"; write(client_fd, response, strlen(response));
HTTP/1.1 200 OK — браузер поймёт, что всё хорошо. Content‑Type: text/html — отправляем HTML. Content‑Length: 13 — указываем длину тела ответа
Теперь клиент увидит в браузере Hello, Habr!.
Финальный код
Объединяем всё:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[1024]; server_fd = socket(AF_INET, SOCK_STREAM, 0); bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(server_fd, 10); while (1) { client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); read(client_fd, buffer, sizeof(buffer) - 1); char response[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Content-Length: 13\r\n" "\r\n" "Hello, Habr!"; write(client_fd, response, strlen(response)); close(client_fd); } close(server_fd); return 0; }
Запускаем сервер, открываем http://localhost:8080 в браузере, и — ваш HTTP‑сервер работает.
Итог
Можно доработать:
Поддержку HTTP/1.1 с Keep‑Alive
Обработку разных GET и POST запросов
Поддержку статических файлов
А если есть идеи для улучшения — пишите в комментариях.
Рекомендую обратить внимание на открытые уроки по C, которые скоро пройдут в Otus:
