2009-09-09 13 views
7

Estoy implementando un cliente HTTP simple que solo se conecta a un servidor web y obtiene su página de inicio predeterminada. Aquí es agradable y funciona:Cliente HTTP realmente extraño usando TcpClient en C#

using System; 
using System.Net.Sockets; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TcpClient tc = new TcpClient(); 
      tc.Connect("www.google.com", 80); 

      using (NetworkStream ns = tc.GetStream()) 
      { 
       System.IO.StreamWriter sw = new System.IO.StreamWriter(ns); 
       System.IO.StreamReader sr = new System.IO.StreamReader(ns); 

       string req = ""; 
       req += "GET/HTTP/1.0\r\n"; 
       req += "Host: www.google.com\r\n"; 
       req += "\r\n"; 

       sw.Write(req); 
       sw.Flush(); 

       Console.WriteLine("[reading...]"); 
       Console.WriteLine(sr.ReadToEnd()); 
      } 
      tc.Close(); 
      Console.WriteLine("[done!]"); 
      Console.ReadKey(); 
     } 
    } 
} 

Al eliminar la línea de abajo desde arriba de código, los bloques de programa en sr.ReadToEnd.

req += "Host: www.google.com\r\n"; 

incluso sustituyen sr.ReadToEnd con sr.Read, pero no puede leer nada. Solía ​​Wireshark para ver lo que suceda de:

Screenshot of captured packets using Wireshark http://www.imagechicken.com/uploads/1252514718052893500.jpg

Como se ve, después de mi solicitud GET Google no responde la solicitud y se retransmite una y otra vez. Parece que DEBEMOS especificar la parte Host en la solicitud HTTP. La parte rara es que NO LO HACEMOS. Usé telnet para enviar esta solicitud y recibí la respuesta de Google. También capturé la solicitud enviada por telnet y era exactamente lo mismo que mi pedido.

Probé en muchos otros sitios web (por ejemplo, Yahoo, Microsoft) pero el resultado es el mismo.

Por lo tanto, ¿el retraso en telnet hacer que el acto servidor web diferente (ya que en realidad nos telnet tipo de caracteres en lugar de enviarlos juntos en 1 paquete).


Otro problema es raro cuando cambio HTTP/1.0 a HTTP/1.1, el programa siempre bloques en sr.ReadToEnd línea. Supongo que es porque el servidor web no cierra la conexión.

Una solución es utilizar Leer (o ReadLine) y ns.DataAvailable para leer la respuesta. Pero no puedo estar seguro de haber leído toda la respuesta. ¿Cómo puedo leer la respuesta y estar seguro de que no quedan más bytes en la respuesta de una solicitud HTTP/1.1?


Nota: Como dice W3,

the Host request-header field MUST accompany all HTTP/1.1 requests

(y lo hice por mi HTTP/1.1 peticiones). Pero no he visto tal cosa para HTTP/1.0. También enviando una solicitud sin Host encabezado utilizando telnet funciona sin ningún problema.


Actualización:

empuje indicador se ha establecido en 1 en el segmento TCP. También intenté netsh winsock reset para restablecer mi pila de TCP/IP. No hay firewalls ni antivirus en la computadora de prueba. El paquete se envía realmente porque Wireshark instalado en otra computadora puede capturarlo.

También he intentado algunas otras solicitudes. Por ejemplo,

string req = ""; 
req += "GET/HTTP/1.0\r\n"; 
req += "s df slkjfd sdf/ s/fd \\sdf/\\\\dsfdsf \r\n"; 
req += "qwretyuiopasdfghjkl\r\n"; 
req += "Host: www.google.com\r\n"; 
req += "\r\n"; 

En todo tipo de peticiones, si omito el anfitrión : parte, el servidor web no responde y si con un Anfitrión: parte, incluso una solicitud válida (solo como la solicitud anterior) se responderá (mediante 400: HTTP Bad Request).

nos dice Host: no es necesario en su equipo, y esto hace que la situación sea más extraña.

+0

No sé si este es el problema, pero no debería utilizar el contenido de longitud en la respuesta HTTP para determinar cuántos bytes debe leer, y luego leer los muchos bytes desde el cuerpo de la respuesta ? – Aziz

+0

@Aziz. Quizás esta sea una buena solución en lugar de usar ** ReadToEnd **. Pero en la primera parte de la pregunta, no recibo nada (ni siquiera un byte) del servidor. – Isaac

+0

Ese código funciona aquí con o sin el encabezado Host :. ¿El segmento TCP de la solicitud GET establece el bit PUSH? - No se puede hacer mucho al respecto, pero si no está configurado, podría explicar las retransmisiones – nos

Respuesta

0

Trate de usar System.Net.WebClient en lugar de directamente System.Net.Sockets.TcpClient:

using System; 
using System.Net; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      WebClient wc = new WebClient(); 
      Console.WriteLine("[requesting...]"); 
      Console.WriteLine(wc.DownloadString("http://www.google.com")); 
      Console.WriteLine("[done!]"); 
      Console.ReadKey(); 
     } 
    } 
} 
+1

@Remy Lebeau - Gracias pero ** DEBO ** usar TcpClient porque quiero hacer esto en un nivel inferior. – Isaac

+0

@Remy Lebeau - Entonces esta no es una respuesta a la pregunta y solo distrae a los demás porque piensan "él tiene una respuesta":/ – Isaac

+3

@isaac - Si debe usar TcpClient, entonces realmente necesita leer las especificaciones HTTP reales en http://www.ietf.org/rfc/rfc2616.txt. Su código de lectura original NO funcionará en muchas situaciones, ya que ReadToEnd() es la forma incorrecta de manejarlas, como dijo Aziz anteriormente. –

2

me encontré con una pregunta en todo eso:

¿Cómo puedo leer la respuesta y estar ¿Debo leer toda la respuesta en la solicitud HTTP/1.1?

¡Y esa es una pregunta que puedo responder!

Todos los métodos que está utilizando aquí son síncronos, lo que es fácil de usar, pero ni siquiera ligeramente confiable. Verás problemas tan pronto como tengas una respuesta considerable y solo obtengas una parte.

Para implementar una conexión TcpClient de forma más robusta, debe usar todos los métodos y devoluciones de llamada asincrónicos. Los métodos correspondientes son los siguientes:

1) Crear la conexión con TcpClient.BeginConnect (...) con la devolución de llamada llamando TcpClient.EndConnect (...)
2) Enviar una solicitud con TcpClient.GetStream() .BeginWrite (...) con la devolución de llamada que llama a TcpClient.GetStream(). EndWrite (...)
3) Recibe una respuesta con TcpClient.GetStream(). BeginRead (...) con la devolución de llamada que llama a TcpClient.GetStream() .EndRead (...), anexando el resultado a un buffer StringBuilder, y luego llamando a TcpClient.GetStream(). BeginRead (...) nuevamente (con la misma devolución de llamada) hasta que se reciba una respuesta de 0 bytes.

Es ese paso final (llamar repetidamente a BeginRead hasta que se leen 0 bytes) que resuelve el problema de obtener la respuesta, toda la respuesta y nada más que la respuesta. Así que ayúdenos TCP.

Espero que ayude!

0

Le sugiero que pruebe su código contra un servidor web estándar, bien probado y ampliamente aceptado, instalado en su máquina local, como Apache HTTPD o IIS.

Configure su servidor web para que responda sin el encabezado Host (por ejemplo, una aplicación web predeterminada en IIS) y vea si todo va bien.

En la línea de fondo, no se puede decir realmente lo que pasa detrás de las escenas, ya que usted no controla los sitios web/aplicaciones web como Google, Yahoo, etc.
Por ejemplo, un administrador del sitio web puede configure el sitio para que no haya una aplicación predeterminada para las conexiones TCP entrantes en el puerto 80, usando el protocolo HTTP.
Pero él/ella puede querer configurar una aplicación telnet por defecto cuando se conecta a través del puerto TCP 23, usando el protocolo TELNET.

3

Esto se refiere al uso de TcpClient.

Sé que esta publicación es antigua. Proporciono esta información solo en caso de que alguien más se encuentre con esto. Considere esta respuesta como un suplemento a todas las respuestas anteriores.

Algunos servidores requieren el encabezado de host HTTP, ya que están configurados para alojar más de un dominio por dirección IP. Como regla general, siempre envía el encabezado del host. Un buen servidor responderá con "No encontrado". Algunos servidores no responderán en absoluto.

Cuando la llamada para leer datos de la secuencia bloquea, generalmente se debe a que el servidor está esperando que se envíen más datos. Este suele ser el caso cuando la especificación HTTP 1.1 no se sigue de cerca. Para demostrar esto, intente omitir la secuencia CR LF final y luego lea los datos de la transmisión: la llamada a leer esperará hasta que el cliente agote el tiempo de espera o hasta que el servidor deje de esperar al finalizar la conexión.

espero que esto arroja un poco de luz ...

0

Creo ReadToEnd esperará hasta que se cierre la conexión. Sin embargo, parece que no se cierra. Deberías leerlo continuamente en su lugar. Entonces funcionará como se espera.

//Console.WriteLine(sr.ReadToEnd()); 
var bufout = new byte[1024]; 
int readlen=0; 
do 
{ 
    readlen = ns.Read(bufout, 0, bufout.Length); 
    Console.Write(System.Text.Encoding.UTF8.GetString(bufout, 0, readlen)); 
} while (readlen != 0);