C # NetworkStream.Читать странность - PullRequest
8 голосов
/ 03 февраля 2010

Кто-нибудь может указать на недостаток в этом коде? Я получаю некоторый HTML с TcpClient. Кажется, что NetworkStream.Read () никогда не завершается при обращении к серверу IIS. Если я вместо этого использую прокси-сервер Fiddler, он работает нормально, но при непосредственном обращении к целевому серверу цикл .read () не завершится, пока не произойдет исключение соединения с ошибкой типа «удаленный сервер закрыл соединение».

internal TcpClient Client { get; set; }

/// bunch of other code here...

try
{

NetworkStream ns = Client.GetStream();
StreamWriter sw = new StreamWriter(ns);

sw.Write(request);
sw.Flush();

byte[] buffer = new byte[1024];

int read=0;

try
{
    while ((read = ns.Read(buffer, 0, buffer.Length)) > 0)
    {
        response.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, read));
    }
}
catch //(SocketException se)
{

}
finally
{
    Close();
}

Обновление

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

Заключение Как уже было сказано, лучше всего воспользоваться преимуществами протокола (в случае HTTP, заголовка Content-Length), чтобы определить, когда транзакция завершена. Однако я обнаружил, что не на всех страницах задана длина контента. Итак, я сейчас использую гибридное решение:

  1. Для ВСЕХ транзакций установите заголовок Connection запроса равным "close", чтобы сервер не оставлял сокет открытым. Это повышает вероятность того, что сервер закроет соединение, когда оно ответит на ваш запрос.

  2. Если установлено Content-Length, используйте его, чтобы определить, когда запрос завершен.

  3. В противном случае установите для свойства NetworkStream RequestTimeout большое, но разумное значение, например, 1 секунду. Затем продолжайте цикл NetworkStream.Read(), пока либо а) не истечет время ожидания, либо б) вы не прочитаете меньше байтов, чем просили.

Спасибо всем за отличные и подробные ответы.

Ответы [ 5 ]

10 голосов
/ 03 февраля 2010

Вопреки тому, что подразумевается в документации для NetworkStream.Read , поток, полученный из TcpClient, не не просто возвращает 0 для количества прочитанных байтов, когда нет данныхдоступно - блокируется.

Если вы посмотрите документацию для TcpClient, вы увидите следующую строку:

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

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

Моим первым предложением было бы устранить StreamWriter в качестве возможной причины (т.е. нюансы буферизации / кодирования) и записать непосредственно в поток, используяNetworkStream.Write метод.Если это работает, убедитесь, что вы используете правильные параметры для StreamWriter.

. Мое второе предложение не должно зависеть от результата вызова Read для разрыва цикла.Класс NetworkStream имеет свойство DataAvailable, предназначенное для этого.Правильный способ написать цикл приема:

NetworkStream netStream = client.GetStream();
int read = 0;
byte[] buffer = new byte[1024];
StringBuilder response = new StringBuilder();
do
{
    read = netStream.Read(buffer, 0, buffer.Length);
    response.Append(Encoding.ASCII.GetString(buffer, 0, read));
}
while (netStream.DataAvailable);
3 голосов
/ 03 февраля 2010

Читайте ответ, пока не достигнете двойного CRLF.Теперь у вас есть заголовки ответа.Разобрать заголовки, чтобы прочитать заголовок Content-Length, который будет количеством байтов, оставшихся в ответе.

Вот регулярное выражение, которое может перехватить заголовок Content-Length.

Обновленное регулярное выражение Дэвида

Content-Length: (?<1>\d+)\r\n

Content-Length

Примечание

Если сервер неправильно установит этот заголовок, я не буду его использовать.

2 голосов
/ 03 февраля 2010

Не уверен, полезно ли это или нет, но с HTTP 1.1 базовое соединение с сервером может не закрыться, поэтому, возможно, поток не закрывается? Идея заключается в том, что вы можете повторно использовать соединение для отправки нового запроса. Я думаю, что вы должны использовать контент-длина. Вместо этого используйте классы WebClient или WebRequest.

1 голос
/ 03 февраля 2010

Я могу ошибаться, но похоже, что ваш вызов Write пишет (под капотом) в поток ns (через StreamWriter). Позже вы читаете из того же потока (ns). Я не совсем понимаю, зачем ты это делаешь?

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

0 голосов
/ 03 февраля 2010

Два предложения ...

  1. Вы пытались использовать свойство DataAvailable NetworkStream? Он должен возвращать true, если есть данные для чтения из потока.

    while (ns.DataAvailable)
    {
     //Do stuff here
    }
  1. Другой вариант - изменить значение ReadTimeOut на низкое, чтобы не блокировать его в течение длительного времени. Это можно сделать так:

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