2009-09-09 10 views
7

Sto implementando un semplice client HTTP che si collega a un server Web e ottiene la sua homepage predefinita. Qui è bello e funziona:Client HTTP davvero strano che utilizza TcpClient in 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(); 
     } 
    } 
} 

Quando si elimina la riga sottostante dall'alto codice, i blocchi di programma su sr.ReadToEnd.

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

Ho anche sostituito sr.ReadToEnd con sr.Read, ma non può leggere nulla. Ho usato Wireshark per vedere che cosa sono accado:

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

Come potete vedere, dopo la mia richiesta GET Google non risponde e la richiesta viene ritrasmessa ancora e ancora. Sembra che dobbiamo specificare la parte dell'host nella richiesta HTTP. La parte strana è NOI NON LO. Ho usato telnet per inviare questa richiesta e ho ricevuto risposta da Google. Ho anche catturato la richiesta inviata da telnet ed era esattamente uguale alla mia richiesta.

Ho provato molti altri siti Web (ad esempio Yahoo, Microsoft) ma il risultato è lo stesso.

Quindi, il ritardo in telnet causa il comportamento del server Web in modo diverso (perché in telnet effettivamente digitare caratteri invece di inviarli insieme in 1 pacchetto).


Un altro problema è strano quando cambio HTTP/1.0 a HTTP/1.1, il programma sempre blocchi sulla sr.ReadToEnd linea. Immagino sia perché il server web non chiude la connessione.

Una soluzione sta usando Leggi (o ReadLine) e ns.DataAvailable leggere la risposta. Ma non posso essere sicuro di aver letto tutta la risposta. Come posso leggere la risposta ed essere sicuro che non ci siano più byte rimasti nella risposta di una richiesta HTTP/1.1?


Nota: Come dice W3,

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

(e l'ho fatto per la mia HTTP/1.1 richieste). Ma non ho visto nulla del genere per HTTP/1.0. Inoltre, l'invio di una richiesta senza l'intestazione Host tramite telnet funziona senza problemi.


Aggiornamento:

push flag è stato impostato a 1 nel segmento TCP. Ho anche provato winsock netsh reset per resettare il mio stack TCP/IP. Non ci sono firewall o anti-virus sul computer di prova. Il pacchetto viene effettivamente inviato perché Wireshark installato su un altro computer può catturarlo.

Ho anche provato altre richieste. Per esempio,

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"; 

In tutti i tipi di richieste, se tralascio l'Ostia : parte, il web-server non risponde e se con un Host: parte, anche una richiesta non valida (solo come la richiesta di cui sopra) sarà risposto (da un 400: HTTP Bad Request).

nos dice che la parte Host: non è richiesta sulla sua macchina, e questo rende la situazione più strana.

+0

Non so se questo è il problema, ma non si dovrebbe utilizzare il contenuto di lunghezza nella risposta HTTP per determinare il numero di byte si dovrebbe leggere, e quindi leggere quei molti byte dal corpo della risposta ? – Aziz

+0

@Aziz. Forse questa è una buona soluzione invece di usare ** ReadToEnd **. Ma nella prima parte della domanda non ricevo nulla (nemmeno un byte) dal server. – Isaac

+0

Quel codice funziona qui con o senza l'intestazione Host :. Il segmento TCP della richiesta GET imposta il bit PUSH? - Non che tu possa fare molto su di esso ma se non è impostato potrebbe spiegare le ritrasmissioni – nos

risposta

0

Provate ad usare System.Net.WebClient invece di System.Net.Sockets.TcpClient direttamente:

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 - Grazie, ma io ** DEVI ** usare TcpClient perché voglio farlo nel livello più basso. – Isaac

+0

@Remy Lebeau - Quindi questa non è una risposta alla domanda e semplicemente distrae gli altri perché pensano "ha una risposta":/ – Isaac

+3

@isaac - Se devi usare TcpClient, allora hai davvero bisogno di leggere le specifiche HTTP attuali a http://www.ietf.org/rfc/rfc2616.txt. Il tuo codice di lettura originale NON funzionerà in molte situazioni, in quanto ReadToEnd() è il modo sbagliato per gestirli, come Aziz ha detto prima. –

2

ho trovato una domanda in tutto ciò che:

Come posso leggere la risposta ed essere sicuro di aver letto tutta la risposta nella richiesta HTTP/1.1?

E questa è una domanda a cui posso rispondere!

Tutti i metodi utilizzati qui sono sincroni, che è facile da usare ma nemmeno leggermente affidabile. Vedrai i problemi non appena avrai una risposta considerevole e ne otterrai solo una parte.

Per implementare una connessione TcpClient in modo più efficace, è necessario utilizzare tutti i metodi asincroni e le richiamate. I metodi rilevanti sono i seguenti:

1) Creare la connessione con TcpClient.BeginConnect (...) con la richiamata chiamando TcpClient.EndConnect (...)
2) Invia una richiesta con TcpClient.GetStream() .BeginWrite (...) con il callback che chiama TcpClient.GetStream(). EndWrite (...)
3) Ricevi una risposta con TcpClient.GetStream(). BeginRead (...) con il callback che chiama TcpClient.GetStream() .EndRead (...), aggiungendo il risultato a un buffer StringBuilder e quindi chiamando TcpClient.GetStream(). BeginRead (...) nuovamente (con lo stesso callback) finché non viene ricevuta una risposta di 0 byte.

È il passaggio finale (che chiama ripetutamente BeginRead fino a 0 byte) che risolve il problema di recuperare la risposta, l'intera risposta e nient'altro che la risposta. Quindi aiutaci TCP.

Spero che questo aiuti!

0

Suggerisco di provare il codice con un server Web standard, ben collaudato e ampiamente accettato, installato sul proprio computer locale, come Apache HTTPD o IIS.

Configura il tuo server web per rispondere senza l'intestazione dell'host (ad esempio un'applicazione Web predefinita in IIS) e vedere se tutto va bene.

In linea di fondo, non si può davvero dire che cosa succede dietro le quinte, dal momento che non si controlla i siti web/applicazioni web come Google, Yahoo, ecc
Per esempio, un amministratore del sito Web può configurare il sito in modo che non ci sia un'applicazione predefinita per le connessioni TCP in ingresso sulla porta 80, utilizzando il protocollo HTTP.
Ma lui/lei potrebbe voler configurare un'applicazione telnet predefinita quando si collega tramite la porta TCP 23, utilizzando il protocollo TELNET.

3

Questo riguarda l'utilizzo di TcpClient.

So che questo post è vecchio. Fornisco queste informazioni solo nel caso in cui qualcun altro si imbatta in questo. Considera questa risposta come un supplemento a tutte le risposte precedenti.

L'intestazione dell'host HTTP è richiesta da alcuni server poiché sono configurati per ospitare più di un dominio per indirizzo IP. Come regola generale, ha sempre inviato l'intestazione Host. Un buon server risponderà con "Not Found". Alcuni server non rispondono affatto.

Quando la chiamata per leggere i dati dallo stream blocca, di solito è perché il server è in attesa di ulteriori dati da inviare. Questo è in genere il caso in cui la specifica HTTP 1.1 non viene seguita da vicino. Per dimostrarlo, prova a omettere la sequenza finale di CR LF e poi leggi i dati dallo stream: la chiamata a leggere attenderà fino a quando il client non scaderà o il server non smetterà di attendere terminando la connessione.

Spero che questo getta un po 'di luce ...

0

Credo ReadToEnd attenderà fino a quando la connessione è chiusa. Tuttavia non sembra chiudersi. Dovresti leggerlo continuamente. Quindi funzionerà come ci si potrebbe aspettare.

//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); 
Problemi correlati