Как получать HTTP-сообщения, используя Socket - PullRequest
8 голосов
/ 31 мая 2010

Я использую Socket класс для своего веб-клиента. Я не могу использовать HttpWebRequest, так как он не поддерживает прокси-носки. Таким образом, я должен разобрать заголовки и самостоятельно обработать кусочное кодирование. Самым сложным для меня является определение длины контента, поэтому я должен читать его побайтно. Сначала я должен использовать ReadByte(), чтобы найти последний заголовок (комбинация "\ r \ n \ r \ n"), затем проверить, есть ли в теле кодировка передачи или нет. Если это так, я должен прочитать размер фрагмента и т. Д .:

public void ParseHeaders(Stream stream)
{
    while (true)
    {
        var lineBuffer = new List<byte>();
        while (true)
        {
            int b = stream.ReadByte();
            if (b == -1) return;
            if (b == 10) break;
            if (b != 13) lineBuffer.Add((byte)b);
        }
        string line = Encoding.ASCII.GetString(lineBuffer.ToArray());
        if (line.Length == 0) break;
        int pos = line.IndexOf(": ");
        if (pos == -1) throw  new VkException("Incorrect header format");
        string key = line.Substring(0, pos);
        string value = line.Substring(pos + 2);
        Headers[key] = value;
    }
}

Но у этого подхода очень низкая производительность. Можете ли вы предложить лучшее решение? Может быть, некоторые примеры или библиотеки с открытым исходным кодом, которые обрабатывают http-запрос через сокеты (хотя и не очень большие и сложные, я нуб). Лучше всего было бы опубликовать ссылку на пример, который читает тело сообщения и правильно обрабатывает случаи, когда: контент имеет chunked-кодировку, кодируется в gzip или deflate, заголовок Content-Length опускается (сообщение заканчивается, когда соединение закрыто). Что-то вроде исходного кода класса HttpWebRequest.

Upd: Моя новая функция выглядит так:

int bytesRead = 0;
byte[] buffer = new byte[0x8000];
do
{
    try
    {
        bytesRead = this.socket.Receive(buffer);
        if (bytesRead <= 0) break;
        else
        {
            this.m_responseData.Write(buffer, 0, bytesRead);
            if (this.m_inHeaders == null) this.GetHeaders();
        }
    }
    catch (Exception exception)
    {
        throw new Exception("Read response failed", exception);
    }
}
while ((this.m_inHeaders == null) || !this.isResponseBodyComplete());

Где GetHeaders() и isResponseBodyComplete() используют m_responseData (MemoryStream) с уже полученными данными.

Ответы [ 9 ]

9 голосов
/ 03 июня 2010

Я предлагаю вам не реализовывать это самостоятельно - протокол HTTP 1.1 достаточно сложен, чтобы сделать его проектом в несколько человеко-месяцев.

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

Преобразование необработанного HTTP-запроса в объект HTTPWebRequest

РЕДАКТИРОВАТЬ: код ротора является достаточно сложным, и трудно читать / перемещаться в виде веб-страниц. Но, тем не менее, усилия по внедрению поддержки SOCKS намного ниже, чем реализация всего протокола HTTP самостоятельно. В течение нескольких дней у вас будет работать что-то, от чего вы можете зависеть, основанное на проверенной и проверенной реализации.

Запрос и ответ считываются из / записываются в NetworkStream, m_Transport в классе Connection. Это используется в следующих методах:

internal int Read(byte[] buffer, int offset, int size) 
//and
private static void ReadCallback(IAsyncResult asyncResult)

оба в http://www.123aspx.com/Rotor/RotorSrc.aspx?rot=42903

Сокет создан в

private void StartConnectionCallback(object state, bool wasSignalled)

Таким образом, вы можете изменить этот метод для создания Socket для вашего socks-сервера и выполнить необходимое рукопожатие для получения внешнего соединения. Остальной код может остаться прежним.

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

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

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

Если проблема заключается в узком месте с точки зрения слишком медленной ReadByte, я предлагаю вам обернуть входной поток StreamBuffer. Если проблема с производительностью, о которой вы заявляете, стоит дорого из-за небольших чтений, то это решит эту проблему для вас.

Кроме того, вам это не нужно:

string line = Encoding.ASCII.GetString(lineBuffer.ToArray()); 

HTTP по замыслу требует, чтобы заголовок состоял только из символов ASCII. Вы действительно не хотите - или не должны - превращать его в реальные строки .NET (которые являются Unicode).

Если вы хотите найти EOF заголовка HTTP, вы можете сделать это для хорошей производительности.

int k = 0;
while (k != 0x0d0a0d0a) 
{
    var ch = stream.ReadByte();
    k = (k << 8) | ch;
}

Когда принята строка \r\n\r\n k будет равна 0x0d0a0d0a

1 голос
/ 01 июня 2010

В большинстве (должно быть во всех) http-запросах должен быть заголовок с именем content-length, который сообщит вам, сколько байтов содержится в теле запроса. Тогда это просто вопрос выделения соответствующего количества байтов и одновременного чтения этих байтов.

0 голосов
/ 10 июня 2010

Почему бы вам не прочитать до 2 новых строк, а затем просто взять из строки? Производительность может быть хуже, но все равно должна быть разумной:

Dim Headers As String = GetHeadersFromRawRequest(ResponseBinary)
   If Headers.IndexOf("Content-Encoding: gzip") > 0 Then

     Dim GzSream As New GZipStream(New MemoryStream(ResponseBinary, Headers.Length + (vbNewLine & vbNewLine).Length, ReadByteSize - Headers.Length), CompressionMode.Decompress)
ClearTextHtml = New StreamReader(GzSream).ReadToEnd()
End If                         

 Private Function GetHeadersFromRawRequest(ByVal request() As Byte) As String

        Dim Req As String = Text.Encoding.ASCII.GetString(request)
        Dim ContentPos As Integer = Req.IndexOf(vbNewLine & vbNewLine)

        If ContentPos = -1 Then Return String.Empty

        Return Req.Substring(0, ContentPos)
    End Function
0 голосов
/ 09 июня 2010

Все ответы здесь о расширении Socket и / или TCPClient, по-видимому, упускают что-то действительно очевидное - HttpWebRequest также является классом и поэтому может быть расширен.написать свой собственный класс HTTP / сокета.Вам просто нужно расширить HttpWebRequest с помощью пользовательского метода подключения.После подключения все данные являются стандартным HTTP и могут обрабатываться базовым классом в обычном режиме.

public class SocksHttpWebRequest : HttpWebRequest

   public static Create( string url, string proxy_url ) {
   ... setup socks connection ...

   // call base HttpWebRequest class Create() with proxy url
   base.Create(proxy_url);
   }

Рукопожатие SOCKS не является особенно сложным, поэтому если у вас есть базовое понимание программирования сокетов, оно не должно заниматьдолго реализовывать связь.После этого HttpWebRequest может выполнять тяжелую работу HTTP.

0 голосов
/ 07 июня 2010

Я бы создал прокси-сервер SOCKS, который может туннелировать HTTP, а затем заставить его принимать запросы от HttpWebRequest и пересылать их. Я думаю, что это было бы гораздо проще, чем воссоздать все, что делает HttpWebRequest. Вы можете начать с Privoxy, или просто бросить свой. Протокол прост и документирован здесь:

http://en.wikipedia.org/wiki/SOCKS

И на RFC, на которые они ссылаются.

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

0 голосов
/ 06 июня 2010

Полезно взглянуть на код другого клиента (если не смущает): http://src.chromium.org/viewvc/chrome/trunk/src/net/http/

Я сейчас тоже что-то делаю. Я считаю, что лучший способ повысить эффективность клиента - использовать предоставленные функции асинхронных сокетов. Они довольно низкоуровневые и сами избавляются от необходимости ждать и иметь дело с потоками. Все они имеют Begin и End в именах своих методов. Но сначала я бы попробовал использовать блокировку, просто чтобы вы не использовали семантику HTTP. Тогда вы можете работать на эффективность. Помните: преждевременная оптимизация - это зло, так что работайте, а потом оптимизируйте все!

Также: часть вашей эффективности может быть связана с использованием ToArray(). Это, как известно, немного дороже в вычислительном отношении. Лучшим решением может быть сохранение промежуточных результатов в буфере byte[] и добавление их к StringBuilder с правильной кодировкой.

Для сжатых или дефлированных данных прочитайте все данные (имейте в виду, что вы можете не получить все данные при первом запросе. Следите, сколько данных вы прочитали, и продолжайте добавлять к тот же буфер). Затем вы можете декодировать данные, используя GZipStream(..., CompressionMode.Decompress).

Я бы сказал, что сделать это не так сложно, как могут показаться некоторые, просто нужно быть немного авантюрным!

0 голосов
/ 06 июня 2010

Хотя я склонен согласиться с mdma в том, что изо всех сил стараюсь избежать реализации своего собственного стека HTTP, вы могли бы рассмотреть одну хитрость - чтение из потоковых блоков среднего размера. Если вы выполняете чтение и даете ему буфер больше, чем доступно, он должен вернуть вам количество байтов, которые он прочитал. Это должно уменьшить количество системных вызовов и значительно повысить производительность. Вам все равно придется сканировать буферы так же, как и сейчас.

0 голосов
/ 01 июня 2010

Возможно, вы захотите взглянуть на класс TcpClient в System.Net, это оболочка для сокета, которая упрощает основные операции.

Оттуда вам придется читать по протоколу HTTP. Также будьте готовы сделать некоторые операции с почтовым индексом. Http 1.1 поддерживает GZip его содержимого и частичных блоков. Тебе придется немного научиться разбирать их вручную.

Базовая Http 1.0 проста, протокол хорошо задокументирован в Интернете, наше дружеское окружение Google может помочь вам с этим.

...