Действительно странный HTTP-клиент, использующий TcpClient в C # - PullRequest
8 голосов
/ 09 сентября 2009

Я реализую простой HTTP-клиент, который просто подключается к веб-серверу и получает его домашнюю страницу по умолчанию. Вот и она работает хорошо:

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.ReadToEnd на sr.Read , но он ничего не может прочитать. Я использовал Wireshark, чтобы увидеть, что случилось:

Скриншот захваченных пакетов с помощью Wireshark http://www.imagechicken.com/uploads/1252514718052893500.jpg

Как видите, после моего запроса GET Google не отвечает, и запрос повторяется снова и снова. Похоже, мы ДОЛЖНЫ указать часть Host в HTTP-запросе. Странная часть МЫ НЕ. Я использовал telnet , чтобы отправить этот запрос и получил ответ от Google. Я также захватил запрос, отправленный telnet, и он был точно таким же, как мой запрос.

Я пробовал много других сайтов (например, Yahoo, Microsoft), но результат тот же.

Таким образом, задержка в telnet заставляет веб-сервер работать иначе (потому что в telnet мы на самом деле печатаем символов вместо того, чтобы отправлять их вместе в 1 пакете).


Другая странная проблема - когда я меняю HTTP / 1.0 на HTTP / 1.1 , программа всегда блокируется в строке sr.ReadToEnd . Я думаю, это потому, что веб-сервер не закрывает соединение.

Одним из решений является использование Read (или ReadLine ) и ns.DataAvailable для чтения ответа. Но я не могу быть уверен, что прочитал все ответы. Как я могу прочитать ответ и убедиться, что в ответе на запрос HTTP / 1.1 больше не осталось байтов?


Примечание: Как говорит W3,

поле заголовка запроса Host ДОЛЖНО сопровождать все HTTP / 1.1 запросы

(и я сделал это для моих запросов HTTP / 1.1). Но я не видел такой вещи для HTTP / 1.0 . Также отправка запроса без заголовка Host с использованием telnet работает без проблем.


Обновление:

Нажмите флаг был установлен в 1 в сегменте TCP. Я также попытался netsh winsock reset , чтобы сбросить мой стек 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";

Во всех видах запросов, если я опускаю часть Host: , веб-сервер не отвечает, а если с частью Host: , даже недопустимый запрос ( так же, как и вышеупомянутый запрос) будет получен ответ (400: HTTP Bad Request).

nos говорит, что Host: часть не требуется на его машине, и это делает ситуацию более странной.

Ответы [ 5 ]

3 голосов
/ 13 июня 2012

Это относится к использованию TcpClient.

Я знаю, что этот пост старый. Я предоставляю эту информацию на случай, если кто-нибудь еще столкнется с этим. Считайте этот ответ дополнением ко всем приведенным выше ответам.

Заголовок узла HTTP требуется для некоторых серверов, поскольку они настроены на размещение более одного домена на один IP-адрес. Как правило, всегда отправляется заголовок хоста. Хороший сервер ответит «Not Found». Некоторые серверы вообще не отвечают.

Когда вызов для чтения данных из потока блокируется, обычно это происходит потому, что сервер ожидает отправки дополнительных данных. Обычно это тот случай, когда спецификация HTTP 1.1 не соблюдается. Чтобы продемонстрировать это, попробуйте пропустить окончательную последовательность CR LF и затем прочитать данные из потока - вызов read будет ждать, пока клиент не истечет время ожидания или сервер не прекратит ожидание, разорвав соединение.

Я надеюсь, что это проливает немного света ...

2 голосов
/ 23 июня 2010

Я нашел один вопрос во всем этом:

Как я могу прочитать ответ и быть уверенным, что прочитал весь ответ в HTTP / 1.1 запросе?

И на этот вопрос я могу ответить!

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

Чтобы реализовать соединение TcpClient наиболее надежно, вы должны использовать все асинхронные методы и обратные вызовы. Соответствующие методы следующие:

1) Создайте соединение с TcpClient.BeginConnect (...) с обратным вызовом, вызвав TcpClient.EndConnect (...)
2) Отправьте запрос с помощью TcpClient.GetStream (). BeginWrite (...) с обратным вызовом, вызвав TcpClient.GetStream (). EndWrite (...)
3) Получить ответ с TcpClient.GetStream (). BeginRead (...) с обратным вызовом, вызывающим TcpClient.GetStream (). EndRead (...), добавив результат в буфер StringBuilder, а затем вызвав TcpClient.GetStream ) .BeginRead (...) снова (с тем же обратным вызовом), пока не будет получен ответ 0 байтов.

Именно этот последний шаг (повторный вызов BeginRead до считывания 0 байтов) решает проблему извлечения ответа, всего ответа и ничего, кроме ответа. Так что помогите нам TCP.

Надеюсь, это поможет!

0 голосов
/ 12 июля 2012

Я полагаю, что 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);
0 голосов
/ 24 июня 2010

Я предлагаю вам попробовать свой код на стандартном, хорошо протестированном, общепринятом веб-сервере, установленном на вашем локальном компьютере, таком как Apache HTTPD или IIS.

Настройте веб-сервер так, чтобы он отвечал без заголовка Host (например, веб-приложение по умолчанию в IIS), и посмотрите, все ли в порядке.

В итоге вы не можете точно сказать, что происходит за кулисами, поскольку вы не управляете веб-сайтами / веб-приложениями, такими как Google, Yahoo и т. Д.
Например, администратор веб-сайта может настроить сайт таким образом, чтобы не было приложения по умолчанию для входящих TCP-соединений через порт 80 с использованием протокола HTTP.
Но он / она может захотеть настроить приложение telnet по умолчанию при подключении через TCP-порт 23, используя протокол TELNET.

0 голосов
/ 10 сентября 2009

Попробуйте использовать System.Net.WebClient вместо 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();
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...