Какой лучший способ реализовать буфер ввода пользователя для сетевого ввода-вывода?

Вопрос или проблема

Я реализую HTTP сервер. Вот класс http_conn.

class http_conn{
  const static int BUF_SIZE = 1024;

  char m_bufin[BUF_SIZE];
  int m_bufinOffset;
};

И я использую событийный цикл с epoll. Когда recv() срабатывает на событии epollin, допустим, получена одна с половиной запроса.

После вызова recv() буфер выглядит следующим образом:

 Входной буфер |----запрос 1-----|--часть req2--|-----пусто----|
 Индекс (байты) 0                  497              608            1023

Мой вопрос в том, как справляться с организацией буфера. После успешного анализа и обработки req1, как поступать с частью req2? Перенести ее в начало буфера, оставить там или скопировать в некий объект Req?

И если использовать epoll в режиме ET, как избежать переполнения буфера?

Да, мой вопрос в том, как реализовать корректный буфер пользовательского ввода.

Ответ или решение

При разработке HTTP-сервера с использованием класса http_conn и обработки событий с помощью epoll, важным аспектом является организация буфера для входящих данных, особенно в случае, когда поступает частичный запрос. Ваша задача заключается в том, чтобы эффективно управлять буфером, что требует понимания того, как обрабатывать полученные данные, минимизируя при этом риски, такие как переполнение буфера.

Организация буфера

В приведенной вами реализации класса http_conn имеется статический размер буфера BUF_SIZE = 1024, который вы используете для хранения входящих данных. Когда вы получаете данные через вызов recv() в событии EPOLLIN, ваш буфер может выглядеть следующим образом:

Input buffer |----request 1-----|--part of req2--|-----empty----|
 Index        0                  497              608           1023

Подходы к обработке буфера

  1. Сдвиг содержимого буфера: После успешной обработки первого запроса (req1), вы можете сдвинуть оставшиеся данные в буфере (часть req2) в начало буфера. Это позволит вам использовать освобожденное пространство в буфере для новых входящих данных. Сдвиг можно реализовать с помощью простого использования функции memmove():

    int req2Length = m_bufinOffset - req1Length; // Длина второго запроса
    memmove(m_bufin, m_bufin + req1Length, req2Length);
    m_bufinOffset = req2Length; // Обновление смещения буфера
  2. Оставить как есть: Другой подход заключается в том, чтобы оставить данные в буфере и обрабатывать второй запрос, только когда он будет полностью получен в следующем вызове recv(). Это может снизить нагрузку на выполнение операций с памятью, однако может привести к ситуации, когда вы можете не учитывать все доступное пространство буфера, если не будете следить за смещением и заполнением.

  3. Использование объекта запроса: Третий подход — это скопировать данные в отдельный объект запроса (например, Req), после того как вы разобрали запрос. Это предоставляет вам больше гибкости при обращении с запросами и упрощает обработку.

    Req newRequest(m_bufin, req1Length);
    // Далее обрабатываем newRequest

Избежание переполнения буфера в режиме ET

Режим Edge Triggered (ET) в epoll требует аккуратного управления буфером, чтобы избежать переполнения. Чтобы предотвратить переполнение, выполните следующие шаги:

  1. Проверка доступного пространства: Перед вызовом recv(), всегда проверяйте, достаточно ли места в буфере для новых данных. Выделите желаемое пространство и допускайте перезапись только до определенного предела.

  2. Контролируйте количество вызовов recv(): Если вы обрабатываете события в режиме ET, лучшая практика заключается в том, чтобы пытаться считывать все доступные данные в одном recv() вызове до тех пор, пока не получите EAGAIN. Это поможет вам избежать ситуации, когда данные остаются в сокете, вызывая следующие события, которые могут переиспользовать уже обработанный буфер.

  3. Управление состояния соединения: Разработайте систему состояния для отслеживания текущего состояния соединения и обработки запросов. Это поможет избежать потери данных, когда вы параллельно обрабатываете несколько запросов.

Заключение

Оптимальная реализация буфера для входящих данных в вашем HTTP-сервере потребует внимательного контроля за состоянием буфера и корректного выделения памяти. Сдвиг буфера после обработки запросов, копирование данных в объект запроса и тщательное управление вызовами recv() помогут создать устойчивую и производительную архитектуру вашего сервера. Не забывайте о проверках на переполнение, чтобы обеспечить корректную работу в режиме Edge Triggered с epoll.

Оцените материал
Добавить комментарий

Капча загружается...