2009-09-09 129 views
7

我正在實現一個簡單的HTTP客戶端,只是連接到一個Web服務器,並獲得其默認主頁。這是和它的作品不錯:真的很奇怪的HTTP客戶端在C#中使用TcpClient

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

當我從上面的代碼刪除下面的線,在sr.ReadToEnd程序塊。

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

我甚至取代sr.ReadToEndsr.Read,但它無法讀取任何東西。我使用Wireshark的,看看有什麼發生:

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

正如你看到的,我的GET請求後,谷歌不響應該請求被一次又一次地重發。看來我們必須在HTTP請求中指定主機部分。奇怪的部分是我們不。我用telnet發送這個請求,並得到了谷歌的迴應。我還捕獲了telnet發送的請求,並且與我的請求完全相同。

我嘗試了很多其他網站(例如雅虎,微軟),但結果是一樣的。

因此,telnet延遲是否會導致web服務器的行爲不同(因爲在telnet中我們實際上是類型是字符,而不是將它們一起發送到1個數據包中)。


另一個奇怪的問題是當我改變HTTP/1.0HTTP/1.1,程序總是塊sr.ReadToEnd線。我想這是因爲Web服務器不關閉連接。

的一個解決方案是使用(或的ReadLine)和ns.DataAvailable讀取響應。但我無法確定我是否已閱讀所有回覆。我如何讀取響應並確保HTTP/1.1請求的響應中沒有剩餘字節?


注: 作爲W3說,

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

(我這樣做是爲了我的HTTP/1.1請求)。但我還沒有看到這樣的事情HTTP/1.0。另外發送請求沒有主機頭使用telnet工作沒有任何問題。


更新:

標誌的TCP段被設置爲1。我也試過netsh winsock重置重置我的TCP/IP協議棧。測試計算機上沒有防火牆和防病毒軟件。數據包實際上被髮送,因爲安裝在另一臺計算機上的Wireshark可以捕獲它。

我也嘗試了一些其他的請求。例如,

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

在所有的請求的類型,如果我省略主持人:一部分,Web服務器不響應,如果有主持人:一部分,甚至是無效的請求(只就像上面的請求一樣)將被響應(通過400:HTTP Bad Request)。

nos主持人:部分是不需要在他的機器上,這使情況更奇怪。

+0

我不知道這是不是問題,但不應該使用HTTP響應中的內容長度來確定您應該讀取多少字節,然後從響應的主體讀取那些字節? – Aziz 2009-09-09 17:00:29

+0

@Aziz。也許這是一個很好的解決方案,而不是使用** ReadToEnd **。但在問題的第一部分中,我沒有收到來自服務器的任何內容(即使是一個字節)。 – Isaac 2009-09-09 17:09:16

+0

這段代碼在有或沒有Host:頭的情況下工作。 GET請求的TCP段是否設置了PUSH位? - 不是你可以做很多事情,但如果沒有設置它可以解釋重發 – nos 2009-09-09 18:48:30

回答

0

嘗試直接使用,而不是System.Net.Sockets.TcpClient System.Net.WebClient:

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 - 謝謝,但我**必須**使用TcpClient,因爲我想在較低級別做到這一點。 – Isaac 2009-09-10 00:32:54

+0

@Remy Lebeau - 所以這不是問題的答案,只是分散他人,因爲他們認爲「他有答案」:/ – Isaac 2009-09-10 07:00:23

+3

@isaac - 如果您必須使用TcpClient,那麼您確實需要閱讀實際的HTTP規範http://www.ietf.org/rfc/rfc2616.txt。由於ReadToEnd()是處理它們的錯誤方式,因此您的原始閱讀代碼在許多情況下都不起作用,就像Aziz先前所說的那樣。 – 2009-09-15 22:50:16

2

我發現所有的一個問題:

我怎樣才能讀取響應,並確定我讀取了HTTP/1.1請求中的所有響應?

這就是我可以回答的問題!

您在這裏使用的所有方法都是同步的,這很容易使用,但是甚至沒有一點可靠。只要你有相當大的迴應,你就會看到問題,只會得到它的一部分。

要最有效地實現TcpClient連接,您應該使用所有異步方法和回調。有關方法如下:

1)創建TcpClient.BeginConnect(...)與回調調用TcpClient.EndConnect(...)
2)發送與TcpClient.GetStream請求的連接() (...)使用回調調用TcpClient.GetStream(...).BeginWrite(...)EndWrite(...)
3)用TcpClient.GetStream()。接收回調調用TcpClient.GetStream ().EndRead(...),將結果附加到StringBuilder緩衝區,然後再次調用TcpClient.GetStream()。BeginRead(...)直到收到0字節的響應(具有相同的回調)。

這是最後一步(反覆調用BeginRead直到讀取0個字節),它解決了獲取響應,整個響應以及響應的問題。所以幫助我們TCP。

希望有幫助!

0

我建議你對安裝在你自己的本地機器上的標準的,經過嚴格測試的,廣泛接受的Web服務器(如Apache HTTPD或IIS)進行測試。

配置您的Web服務器,以便在沒有主機標頭(例如IIS中的默認Web應用程序)的情況下進行響應,並查看是否一切順利。

在底線,你不能真正知道幕後發生了什麼,因爲你不能控制像谷歌,雅虎等網站/網絡應用程序。
例如,網站管理員可以配置站點,以便在端口80上沒有使用HTTP協議的傳入TCP連接的默認應用程序。
但是他/她可能想要在使用TELNET協議通過TCP端口23連接時配置默認的telnet應用程序。

3

這屬於使用TcpClient。

我知道這個帖子是舊的。我提供這些信息是爲了防止任何人遇到這種情況。考慮這個答案是所有上述答案的補充。

某些服務器需要HTTP主機標頭,因爲它們被設置爲爲每個IP地址託管多個域。作爲一般規則,總是發送主機頭。一個好的服務器會回覆「未找到」。有些服務器根本不會回覆。

當從流塊中讀取數據的調用時,通常是因爲服務器正在等待更多要發送的數據。當HTTP 1.1規範未得到嚴格遵守時,通常就是這種情況。爲了演示這一點,嘗試省略最終的CR LF序列,然後從流中讀取數據 - 讀取的調用將等待,直到客戶端超時或服務器通過終止連接放棄等待。

我希望這棚一點光......

0

相信ReadToEnd的將等待,直到連接被關閉。但它似乎並沒有結束。你應該不斷閱讀它。然後它會按照您的預期工作。

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