Вопрос или проблема
Я реализую 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
Подходы к обработке буфера
-
Сдвиг содержимого буфера: После успешной обработки первого запроса (req1), вы можете сдвинуть оставшиеся данные в буфере (часть req2) в начало буфера. Это позволит вам использовать освобожденное пространство в буфере для новых входящих данных. Сдвиг можно реализовать с помощью простого использования функции
memmove()
:int req2Length = m_bufinOffset - req1Length; // Длина второго запроса memmove(m_bufin, m_bufin + req1Length, req2Length); m_bufinOffset = req2Length; // Обновление смещения буфера
-
Оставить как есть: Другой подход заключается в том, чтобы оставить данные в буфере и обрабатывать второй запрос, только когда он будет полностью получен в следующем вызове
recv()
. Это может снизить нагрузку на выполнение операций с памятью, однако может привести к ситуации, когда вы можете не учитывать все доступное пространство буфера, если не будете следить за смещением и заполнением. -
Использование объекта запроса: Третий подход — это скопировать данные в отдельный объект запроса (например,
Req
), после того как вы разобрали запрос. Это предоставляет вам больше гибкости при обращении с запросами и упрощает обработку.Req newRequest(m_bufin, req1Length); // Далее обрабатываем newRequest
Избежание переполнения буфера в режиме ET
Режим Edge Triggered (ET) в epoll
требует аккуратного управления буфером, чтобы избежать переполнения. Чтобы предотвратить переполнение, выполните следующие шаги:
-
Проверка доступного пространства: Перед вызовом
recv()
, всегда проверяйте, достаточно ли места в буфере для новых данных. Выделите желаемое пространство и допускайте перезапись только до определенного предела. -
Контролируйте количество вызовов
recv()
: Если вы обрабатываете события в режиме ET, лучшая практика заключается в том, чтобы пытаться считывать все доступные данные в одномrecv()
вызове до тех пор, пока не получитеEAGAIN
. Это поможет вам избежать ситуации, когда данные остаются в сокете, вызывая следующие события, которые могут переиспользовать уже обработанный буфер. -
Управление состояния соединения: Разработайте систему состояния для отслеживания текущего состояния соединения и обработки запросов. Это поможет избежать потери данных, когда вы параллельно обрабатываете несколько запросов.
Заключение
Оптимальная реализация буфера для входящих данных в вашем HTTP-сервере потребует внимательного контроля за состоянием буфера и корректного выделения памяти. Сдвиг буфера после обработки запросов, копирование данных в объект запроса и тщательное управление вызовами recv()
помогут создать устойчивую и производительную архитектуру вашего сервера. Не забывайте о проверках на переполнение, чтобы обеспечить корректную работу в режиме Edge Triggered с epoll
.