Буферизация данных из сокетов? - PullRequest
4 голосов
/ 26 августа 2010

Я пытаюсь создать простой HTTP-сервер, который сможет анализировать клиентские запросы и отправлять ответы обратно.

Теперь у меня проблема. Я должен читать и обрабатывать одну строку за раз в запросе, и я не знаю, должен ли я:

  • читать по одному байту за раз, или
  • читает фрагменты N байт за раз, помещает их в буфер и затем обрабатывает байты один за другим, прежде чем читать новый фрагмент байтов.

Какой будет лучший вариант, и почему ?

Кроме того, есть ли альтернативные решения для этого? Как функция, которая будет читать строку из сокета или что-то в этом роде?

Ответы [ 6 ]

5 голосов
/ 26 августа 2010

Один байт за раз убивает производительность. Рассмотрим кольцевой буфер приличного размера.

Чтение фрагментов любого размера свободно в буфере. Большую часть времени вы будете получать короткие чтения. Проверьте конец команды http в прочитанных байтах. Обрабатывайте завершенные команды, и следующий байт становится главой буфера. Если буфер заполнится, скопируйте его в резервный буфер, используйте второй кольцевой буфер, сообщите об ошибке или что-то еще подходящее.

4 голосов
/ 26 августа 2010

Короткий ответ на ваш вопрос заключается в том, что я буду читать по одному байту за раз. К сожалению, это один из тех случаев, когда есть плюсы и минусы для обоих случаев.

Для использования буфера является тот факт, что реализация может быть более эффективной с точки зрения сетевого ввода-вывода. Против использования буфера, я думаю, что код будет более сложным, чем однобайтовая версия. Таким образом, компромисс между эффективностью и сложностью. Хорошая новость заключается в том, что вы можете сначала внедрить простое решение, профилировать результат и «обновить» до буферизованного подхода, если тестирование покажет его целесообразность.

Также, просто чтобы заметить, в качестве мысленного эксперимента я написал некоторый псевдокод для цикла, который выполняет чтение http-пакетов на основе буфера, включенный ниже. Сложность реализации буферизованного чтения не кажется плохой. Обратите внимание, что я не уделял много внимания обработке ошибок или проверял, будет ли это работать вообще. Однако следует избегать чрезмерной «двойной обработки» данных, что важно, поскольку это приведет к снижению эффективности, которая была целью этого подхода.

#define CHUNK_SIZE 1024

nextHttpBytesRead = 0;
nextHttp = NULL;
while (1)
{
  size_t httpBytesRead = nextHttpBytesRead;
  size_t thisHttpSize;
  char *http = nextHttp;
  char *temp;
  char *httpTerminator;

  do
  {
    temp = realloc(httpBytesRead + CHUNK_SIZE);
    if (NULL == temp)
      ...
    http = temp;

    httpBytesRead += read(httpSocket, http + httpBytesRead, CHUNK_SIZE);
    httpTerminator = strstr(http, "\r\n\r\n");
  }while (NULL == httpTerminator)

  thisHttpSize = ((int)httpTerminator - (int)http + 4; // Include terminator
  nextHttpBytesRead = httpBytesRead - thisHttpSize;

  // Adding CHUNK_SIZE here means that the first realloc won't have to do any work
  nextHttp = malloc(nextHttpBytesRead + CHUNK_SIZE);
  memcpy(nextHttp,  http + thisHttpSize, nextHttpSize);

  http[thisHttpSize] = '\0';
  processHttp(http);
}
1 голос
/ 27 августа 2010

Indy , например, использует буферизованный подход.Когда код просит Indy прочитать строку, он сначала проверяет свой текущий буфер, чтобы увидеть, присутствует ли разрыв строки.Если нет, сеть считывается порциями и добавляется в буфер до появления разрыва строки.Как только это произойдет, только данные до разрыва строки удаляются из буфера и возвращаются в приложение, оставляя все оставшиеся данные в буфере для следующей операции чтения.Это обеспечивает хороший баланс между простым API-интерфейсом уровня приложения (ReadLine, ReadStream и т. Д.), В то же время обеспечивая эффективный сетевой ввод-вывод (считывание всего, что в данный момент доступно в сокете, его буферизацию и подтверждение его, чтобы отправитель былне ждет слишком долго - для этого требуется меньше операций чтения на уровне сети).

1 голос
/ 26 августа 2010

Поток данных TCP поступает по одному IP-пакету за раз, который может составлять до 1500 или около того байтов в зависимости от конфигурации уровня IP.В Linux это будет ждать в одном SKB, ожидая, пока прикладной уровень не прочитает очередь.Если вы читаете по одному байту за раз, вы испытываете издержки переключения контекста между приложением и ядром для простого копирования одного байта из одной структуры в другую.Оптимальным решением является использование неблокирующего ввода-вывода для считывания содержимого одного SKB за раз и минимизации переключателей.

Если вы используете оптимальную полосу пропускания, вы можете считывать более длинные байты для дальнейшегоуменьшите переключение контекста с дорогой задержкой, так как больше времени будет потрачено из памяти копирования приложения.Но это относится только к крайностям, и такие изменения кода должны быть реализованы при необходимости.

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

Я реализовал библиотеку HTTP-сервера, очень похожую на псевдокод torak , http://code.google.com/p/openpgm/source/browse/trunk/openpgm/pgm/http.cНаибольшие улучшения в скорости для этой реализации были достигнуты за счет асинхронного выполнения всех операций, чтобы ничто не блокировалось.

0 голосов
/ 08 сентября 2012

Чтение буфера массива байтов за раз.Чтение одиночных символов будет медленным из-за множественных переключений контекста между режимом пользователя и ядра (в зависимости от фактически libc).

Если вы читаете буферы, вы должны быть готовы к тому, что буфер может быть не заполненполностью (следите за возвращением длины), что буфер не содержит достаточного количества байтов для конца строки или что буфер содержит более одной строки.

В сетевых приложениях обычно используется схема отображения вашей строки или исправлена-блокировать запросы размера к этой переменной паре буферов (и часто реализуются неправильно, например, возможен ответ длиной 0 байт).Высшие языки скроют вас от этой сложности.

0 голосов
/ 26 августа 2010

Начните с чтения одного байта за раз (хотя обратите внимание, что строки заканчиваются на cr / lf в HTTP), потому что это просто.Если этого недостаточно, делайте более сложные вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...