В настоящее время я пишу очень простой веб-сервер, чтобы узнать больше о программировании сокетов низкого уровня. В частности, я использую C ++ в качестве основного языка и пытаюсь инкапсулировать системные вызовы C низкого уровня в классах C ++ с помощью API более высокого уровня.
Я написал класс Socket
, который управляет дескриптором файла сокета и управляет открытием и закрытием с помощью RAII. Этот класс также предоставляет стандартные операции с сокетами для сокетов, ориентированных на соединение (TCP), такие как bind, listen, accept, connect и т. Д.
После прочтения man-страниц для системных вызовов send и recv я понял, что мне нужно вызывать эти функции внутри некоторой формы цикла, чтобы гарантировать, что все байты успешно отправлено / получено.
Мой API для отправки и получения выглядит примерно так
void SendBytes(const std::vector<std::uint8_t>& bytes) const;
void SendStr(const std::string& str) const;
std::vector<std::uint8_t> ReceiveBytes() const;
std::string ReceiveStr() const;
Для функции отправки я решил использовать блокирующий send
вызов внутри цикла, такого как этот (это внутренняя вспомогательная функция, которая работает как для std :: string, так и для std :: vector).
template<typename T>
void Send(const int fd, const T& bytes)
{
using ValueType = typename T::value_type;
using SizeType = typename T::size_type;
const ValueType *const data{bytes.data()};
SizeType bytesToSend{bytes.size()};
SizeType bytesSent{0};
while (bytesToSend > 0)
{
const ValueType *const buf{data + bytesSent};
const ssize_t retVal{send(fd, buf, bytesToSend, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to send."};
}
const SizeType sent{static_cast<SizeType>(retVal)};
bytesSent += sent;
bytesToSend -= sent;
}
}
Кажется, что это работает нормально и гарантирует, что все байты будут отправлены после того, как функция-член вернется без выдачи исключения.
Однако у меня начались проблемы, когда я начал реализовывать функцию приема. Для моей первой попытки я использовал блокирующий вызов recv
внутри цикла и вышел из цикла, если recv
вернул 0, указывая, что основное TCP-соединение было закрыто.
template<typename T>
T Receive(const int fd)
{
using SizeType = typename T::size_type;
using ValueType = typename T::value_type;
T result;
const SizeType bufSize{1024};
ValueType buf[bufSize];
while (true)
{
const ssize_t retVal{recv(fd, buf, bufSize, 0)};
if (retVal < 0)
{
throw ch::NetworkError{"Failed to receive."};
}
if (retVal == 0)
{
break; /* Connection is closed. */
}
const SizeType offset{static_cast<SizeType>(retVal)};
result.insert(std::end(result), buf, buf + offset);
}
return result;
}
Это работает нормально, если отправитель закрывает соединение после отправки всех байтов. Однако это не тот случай, когда, например, Chrome для запроса веб-страницы. Соединение остается открытым, и моя функция приема-члена блокируется при системном вызове recv
после получения всех байтов в запросе. Мне удалось обойти эту проблему, установив таймаут для вызова recv
с помощью setsockopt . По сути, я возвращаю все байты, полученные на данный момент, по истечении времени ожидания. Это выглядит как очень не элегантное решение, и я не думаю, что именно так веб-серверы решают эту проблему в реальности.
Итак, на мой вопрос.
Как веб-сервер узнает, когда HTTP-запрос был полностью получен?
Запрос GET
в HTTP 1.1, похоже, не включает заголовок Content-Length. Смотрите, например эта ссылка .