2010-02-03 5 views
6

¿Alguien puede señalar el error en este código? Estoy recuperando algo de HTML con TcpClient. NetworkStream.Read() nunca parece terminar cuando habla con un servidor IIS. Si utilizo el proxy Fiddler, funciona bien, pero cuando se habla directamente con el servidor de destino, el ciclo .read() no saldrá hasta que la conexión salga con un error como "el servidor remoto ha cerrado la conexión".C# NetworkStream.Read oddity

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(); 
} 

actualización

En el depurador, puedo ver toda la respuesta que viene a través inmediatamente y se añade a mis StringBuilder (respuesta). Simplemente parece que la conexión no se cierra cuando el servidor termina de enviar la respuesta o mi código no la detecta.

Conclusión Como se ha dicho aquí, lo mejor es aprovechar las ofertas del protocolo (en el caso de HTTP, la cabecera Content-Length) para determinar cuando una transacción se ha completado. Sin embargo, descubrí que no todas las páginas tienen una longitud de contenido establecida. Por lo tanto, ahora estoy usando una solución híbrida:

  1. para todas las transacciones, establecer la cabecera de la solicitud Connection para "cerrar", por lo que el servidor no se le aconseja mantener el socket abierto. Esto mejora las posibilidades de que el servidor cierre la conexión cuando está respondiendo a su solicitud.

  2. Si está configurado Content-Length, utilícelo para determinar cuándo se completa una solicitud.

  3. De lo contrario, establezca la propiedad RequestTimeout de NetworkStream en un valor grande, pero razonable, como 1 segundo. A continuación, realice un bucle en NetworkStream.Read() hasta que a) se agote el tiempo de espera, o b) lea menos bytes de los que solicitó.

Gracias a todos por sus excelentes y detalladas respuestas.

+0

Creo que deberías escribir la solicitud por partes. Esto te ayudará a depurar. – ChaosPandion

+0

La escritura está funcionando bien; es la lectura la que está causando el problema. –

+0

¿Está bloqueando en la llamada 'Lectura', o está obteniendo una ejecución infinita del ciclo? Si es el último, ¿qué está saliendo de la corriente? ¿Has verificado el contenido de 'respuesta'? – Aaronaught

Respuesta

2

No estoy seguro si esto es útil o no, pero con HTTP 1.1, la conexión subyacente al servidor no podría ser cerrado así que tal vez la corriente no quede cerrado o bien? La idea es que puedas reutilizar la conexión para enviar una nueva solicitud. Creo que debes usar la longitud del contenido. Alternativamente, use las clases WebClient o WebRequest en su lugar.

+0

Al agregar el encabezado "Conexión: cerrar" se corrigió esto, y parece funcionar para casi todo. Buena llamada. –

3

Lea la respuesta hasta que llegue a un CRLF doble. Lo que ahora tienes son los encabezados de respuesta. Analice los encabezados para leer el encabezado Content-Length, que será el recuento de bytes que quedan en la respuesta.

Aquí hay una expresión regular que puede capturar el encabezado Content-Length.

de David Actualizado Regex

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

Content-Length

Nota

Si el servidor no establece adecuadamente esta cabecera que no lo usaría.

+0

+1 para mayor claridad y la expresión regular. Gracias. –

+0

+1 para tomar al día el problema 'content-length' y poner un ejemplo. Parece que a menudo hay un problema más profundo detrás de la pregunta. – Aaronaught

+0

Vea también: http://en.wikipedia.org/wiki/Chunked_transfer_encoding Compruebe esto si no hay un encabezado de longitud de contenido. – Foole

1

Puedo estar equivocado, pero parece que su llamada a Write está escribiendo (debajo del capó) en la corriente ns (a través de StreamWriter). Más tarde, está leyendo desde la misma transmisión (ns). No entiendo muy bien por qué estás haciendo esto?

De todos modos, puede necesitar utilizar Seek en la transmisión, para ir al lugar donde desea comenzar a leer. Supongo que busca hasta el final después de escribir. Pero como dije, ¡no estoy seguro si esta es una respuesta útil!

+2

Así es como funciona 'NetworkStream'. Intentar buscar en uno siempre arrojará una 'NotSupportedException'. – Aaronaught

+0

Tomas, NetworkStream está vinculado a un canal IP almacenado. La escritura envía datos al servidor, Reading intenta leer de un búfer de recepción. .Seek() no tiene sentido en ese contexto. –

+0

¡Gracias por la aclaración! ¡Me alegro de que hayas recibido una mejor respuesta! –

9

Contrariamente a lo que la documentación de NetworkStream.Read implica, la corriente obtenida de un TcpClient hace no devuelva 0 para el número de bytes leídos cuando no hay datos disponibles - bloquea.

Si nos fijamos en la documentation for TcpClient, verá esta línea:

La clase TcpClient proporciona métodos simples para la conexión, enviando y recibiendo datos de la secuencia a través de una red en modo de bloqueo sincrónica.

Ahora supongo que si su llamada Read está bloqueando, es porque el servidor ha decidido no enviar ningún dato de regreso. Esto es probablemente porque la solicitud inicial no se está procesando correctamente.

Mi primera sugerencia sería eliminar el StreamWriter como una posible causa (es decir, matices de búfer/codificación) y escribir directamente en la secuencia mediante el método NetworkStream.Write. Si eso funciona, asegúrese de estar usando los parámetros correctos para el StreamWriter.

Mi segunda sugerencia sería no depender del resultado de una llamada Read para romper el ciclo. La clase NetworkStream tiene una propiedad DataAvailable diseñada para esto. La forma correcta de escribir un bucle recibir es:

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); 
+0

Una vez más, la solicitud funciona bien al pasar por el proxy Fiddler. Puedo ver toda la respuesta llegando y adjuntada a mi StringBuilder (respuesta). Simplemente parece que la conexión no se cierra cuando el servidor termina de enviar la respuesta o mi código no la detecta. Argh. –

+0

@David: ver mi actualización, agregué un ejemplo de cómo escribir el bucle usando 'DataAvailable' en lugar de simplemente bloquear en cada lectura. Si esto también falla, significa que no recibirá ninguna respuesta del servidor cuando no utilice Fiddler. – Aaronaught

+0

@Aaronaught Incluso cuando va directo (no a través de Fiddler), I * AM * recibe la respuesta * COMPLETA que espero (puedo ver esto en el depurador). Simplemente no recibo ningún tipo de indicación de que la transacción esté completa una vez que eso sucede. Además, ¿DataAvailable no indica que hay datos en el búfer de recepción? Si ese es el caso, un valor falso para DataAvailable puede no indicar necesariamente que la transacción está completa, solo que no hay datos disponibles, o que el servidor se está tomando su tiempo antes de enviar el siguiente fragmento. –

0

Dos sugerencias ...

  1. ¿Ha intentado utilizar la propiedad DataAvailable de NetworkStream? Debería volverse verdadero si hay datos para leer de la transmisión.

    while (ns.DataAvailable) 
    { 
    //Do stuff here 
    } 
  1. Otra opción sería cambiar la ReadTimeout a un valor bajo para que no terminen el bloqueo durante mucho tiempo. Se puede hacer de la siguiente manera:

    ns.ReadTimeOut=100; 
+0

Me preocupa que cuando el servidor IIS de destino está bajo una gran carga, esto podría hacer que cierre prematuramente el socket. Creo que DataAvailable indica que hay datos en el búfer de recepción; si es falso, el servidor aún puede estar procesando datos para enviar. Establecer un tiempo de espera bajo podría causar el mismo problema. –