Как стать автором
Обновить
610.52
OTUS
Цифровые навыки от ведущих экспертов

Пишем HTTP-сервер на чистом C

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров11K

Привет, Хабр!

Сегодня будем писать 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:

  • 11 марта. Практический Си: Разрабатываем игру-викторину. Подробнее

  • 24 марта. Язык и алгоритмы: Увлекательное путешествие в лексический анализ на C. Подробнее

Теги:
Хабы:
Всего голосов 21: ↑8 и ↓13-2
Комментарии27

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS