.Net C # TcpClient / Socket Производительность / эффективность HTTP-клиента

Я пишу HTTP-клиент, используя .Net TcpClient / Sockets.

Пока что клиент обрабатывает как Content-Length, так и фрагментированные ответы, повторяя ответ NetworkStream (после записи запроса GET в TcpClient), анализируя заголовки и извлекая соответствующие байты тела сообщения / фрагментированные байты. Для этого он использует метод NetworkStream ReadByte.

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

Первоначально это будет включать замену ReadByte на Read для тела сообщения (на основе Content-Length) или извлечение байтов тела сообщения по фрагментам в буфер подходящего размера с использованием ReadByte во всех других областях (например, чтение заголовков, размеров фрагментов и т. Д.).

Мне интересно узнать мысли о лучших / различных способах сделать это для достижения оптимальной производительности? Очевидно, что основная проблема HTTP - это незнание длины потока ответа, если он не анализируется при получении.

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

Огромное спасибо,

Крис


person Chris    schedule 01.12.2009    source источник
comment
Кстати, мне любопытно - вы упомянули, что вам нужен точный контроль над Socket / Connection, поэтому вы не используете HttpWebRequest. Вы можете рассказать нам об этих причинах?   -  person feroze    schedule 02.12.2009
comment
Я хочу смоделировать несколько (скажем, 200) веб-клиентов, и для этого я хочу, чтобы каждый клиент работал как поток, который управляет своим собственным соединением, сохраняя его открытым в течение нескольких различных запросов, пауз и т. Д. Каждый клиент будет достигать той же конечной точки (функциональность очень похожа на стандартные инструменты нагрузочного тестирования). Я не верю, что HttpWebRequest может достичь этого, поскольку соединения обрабатываются через точку обслуживания, которая останавливает этот уровень детального контроля?   -  person Chris    schedule 02.12.2009
comment
Извините, я также должен сказать, что мне нужно захватить такую ​​информацию, как время до первого байта, количество повторных подключений и т. Д.   -  person Chris    schedule 02.12.2009
comment
Крис, вы можете добиться желаемого с помощью HttpWebRequest. Если вы хотите, чтобы у каждого потока было собственное соединение, установите для параметра ConnectionGroupName объекта запроса значение уникальное. Тогда это соединение не будет передано другим запросам. Вы можете закрыть соединение, вызвав CloseConnectionGroup для connectionGroup, используемой запросом. На вашем месте я бы сначала попытался использовать HttpWebRequest и посмотреть, подходит ли он для моей цели. Если этого не произойдет, я бы сам свернул.   -  person feroze    schedule 02.12.2009


Ответы (1)


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

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

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

Изменить

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

person John Fisher    schedule 01.12.2009
comment
Спасибо, Джон. Однако, если я использую буфер, я предполагаю, что мне нужно использовать Read (), который будет блокироваться при превышении конца потока. Мне нужно сразу перейти, когда будет прочитан последний байт, для дальнейшего анализа ответа и т. Д.? Кроме того, есть ли у вас какие-либо примеры / ссылки на аналогичную систему, основанную на событиях? Спасибо! - person Chris; 01.12.2009
comment
Вы можете предотвратить блокировку, отправив Connection: close как часть запроса. - person Matthew Whited; 01.12.2009
comment
Извините, я все еще не понимаю этого. Если я создаю буфер с самого начала и использую Read () для чтения потока в него, есть вероятность, что принимаемый поток меньше, чем длина буфера, например: myStream.Read (myReadBuffer, 0, myReadBuffer.Length); и поэтому он будет блокироваться до тех пор, пока тайм-аут / соединение не будет закрыто. Я не хочу этого, потому что хочу обработать данные ответа как можно скорее .... - person Chris; 02.12.2009
comment
.... Не могли бы вы подтвердить, как лучше всего прочитать ответ HTTP, чтобы прочитать до конца потока как можно быстрее? Для ответов, содержащих заголовок Content-Length, будет ли это ReadByte (), чтобы добраться до конца заголовка, а затем прочитать в массиве байтов размером Content-Length? А для фрагментированных ответов то же самое, но затем ReadByte, чтобы получить размер фрагмента, и Считать в массив byte [] размер соответствующего фрагмента? - person Chris; 02.12.2009
comment
Вы можете вызвать Read, передав число меньше myReadBuffer.Length. Это позволит вам прочитать некоторое минимальное количество байтов, что позволит вам определить длину документа, прежде чем читать остальные. - person John Fisher; 02.12.2009
comment
Крис, похоже, вы хотите читать очень маленькие куски, пока не найдете длину содержимого. Как только вы его нашли, прочтите его и продолжайте заполнять буфер с помощью .Read (buffer, alreadyReadBytes, buffer.Length - alreadyReadBytes) - person John Fisher; 02.12.2009