Как получать HTTP-сообщения с помощью сокета
Я использую Socket
класс для моего веб-клиента. Я не могу использовать HttpWebRequest
поскольку он не поддерживает прокси-серверы socks. Поэтому я должен анализировать заголовки и обрабатывать кодировку chunked самостоятельно. Самое сложное для меня-определить длину контента, поэтому я должен читать его байт за байтом. Сначала я должен использовать ReadByte()
чтобы найти последний заголовок (комбинация" rnrn"), затем проверьте, имеет ли тело кодировку передачи или нет. Если это так, я должен прочитать размер куска и т. д.:
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-запрос через сокеты (не очень большие и сложные, хотя я noob). Лучше всего было бы опубликовать ссылку на пример, который читает тело сообщения и правильно обрабатывает случаи, когда: содержимое имеет кодировку chunked, является gzip-или deflate - encoded, заголовок 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 ответов
Я предлагаю вам не реализовывать это самостоятельно-протокол HTTP 1.1 достаточно сложен, чтобы сделать этот проект из нескольких человеко-месяцев.
вопрос в том, есть ли анализатор протокола HTTP-запросов для .NET? Этот вопрос был задан на SO, и в ответах вы увидите несколько предложений, включая исходный код для обработки HTTP-потоков.
преобразование необработанного HTTP-запроса в объект HTTPWebRequest
EDIT: Ротор код достаточно сложный и трудный для чтения / навигации в качестве веб-страниц. Но все же усилия по реализации добавления поддержки 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)
таким образом, вы можете изменить этот метод, чтобы создать сокет для вашего сервера socks, и сделать необходимое рукопожатие для получения внешнего соединения. Остальная часть кода может остаться прежней.
Я gammered эту информацию в 30 минут, глядя на страницы в интернете. Это должно идти намного быстрее, если вы загрузите их файлы в IDE. Это может показаться бременем, чтобы прочитать этот код - в конце концов, чтение кода намного сложнее, чем его написание, но вы делаете только небольшие изменения в уже установленной, рабочей системе.
чтобы убедиться, что изменения работают во всех случаях, будет разумно также проверить, когда соединение нарушено , чтобы гарантировать, что клиент повторно подключается с помощью того же метода, и поэтому восстанавливает соединение SOCKS и отправляет запрос SOCKS.
если проблема является узким местом с точки зрения ReadByte
будучи слишком медленным, я предлагаю вам обернуть входной поток с StreamBuffer
. Если проблема производительности, которую вы утверждаете, стоит дорого из-за небольших чтений, то это решит проблему для вас.
кроме того, вам не нужно это:
string line = Encoding.ASCII.GetString(lineBuffer.ToArray());
HTTP по дизайну требует, чтобы заголовок состоял только из символов ASCII. Вы действительно не хотите-или должны-превратить его в фактические строки .NET (которые Юникод.)
если вы хотите найти EOF заголовка HTTP, вы можете сделать это для хорошей производительности.
int k = 0;
while (k != 0x0d0a0d0a)
{
var ch = stream.ReadByte();
k = (k << 8) | ch;
}
после строки \r\n\r\n
это обнаружил k
будет равна 0x0d0a0d0a
в большинстве (должны быть все) http-запросов должен быть заголовок content-length, который скажет вам, сколько байтов есть в теле запроса. Тогда это просто вопрос выделения соответствующего количества байтов и чтения этих байтов сразу.
хотя я склонен согласиться с mdma о попытке как можно больше избежать реализации собственного http-стека, один трюк, который вы могли бы рассмотреть,-это чтение из кусков потока среднего размера. Если вы делаете чтение и даете ему буфер, который больше, чем то, что доступно, он должен вернуть вам количество байтов, которые он прочитал. Это должно уменьшить количество системных вызовов и значительно ускорить вашу производительность. Вам все равно придется сканировать буферы, как и сейчас, хотя.
взглянуть на код другого клиента полезно (если не путать): http://src.chromium.org/viewvc/chrome/trunk/src/net/http/
в настоящее время я тоже делаю что-то подобное. Я нахожу лучший способ повысить эффективность клиента-использовать предоставленные функции асинхронных сокетов. Они довольно низкоуровневые и избавляются от напряженного ожидания и работы с потоками самостоятельно. Все они имеют Begin
и End
в названии метода. Но сначала я попробовал бы использовать блокировку, просто чтобы вы убрали семантику HTTP с пути. Тогда вы сможете работать над эффективностью. Помните: преждевременная оптимизация-это зло, поэтому заставьте ее работать, а затем оптимизируйте все!
кроме того: некоторые из вашей эффективности могут быть связаны с использованием ToArray()
. Известно, что это немного дорого с точки зрения вычислений. Лучшим решением может быть сохранение промежуточных результатов в byte[]
buffer и добавьте их в StringBuilder
С правильным кодирование.
для данных gzipped или deflated, прочитайте все данные (имейте в виду, что вы можете не получить все данные при первом запросе. Следите за тем, сколько данных вы прочитали, и продолжайте добавлять в тот же буфер). Затем вы можете декодировать данные с помощью GZipStream(..., CompressionMode.Decompress)
.
Я бы сказал, что это не так сложно, как некоторые могут предполагать, вы просто должны быть немного авантюрным!
все ответы здесь о расширении сокета и / или TCPClient, похоже, упускают что - то действительно очевидное-что HttpWebRequest также является класс и поэтому может быть расширен.
вам не нужно писать свой собственный класс HTTP / socket. Вам просто нужно расширить 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);
}
рукопожатие носков не особенно сложный, поэтому, если у вас есть базовое понимание программирования сокетов, это не займет много времени, чтобы реализовать соединение. После этого HttpWebRequest может выполнять тяжелый подъем HTTP.
почему бы вам не прочитать до 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
Вы можете посмотреть на TcpClient
класс System.Net
, Это оболочка для сокета, которая упрощает основные операции.
оттуда вам придется прочитать протокол HTTP. Также будьте готовы сделать некоторые операции zip. Http 1.1 поддерживает GZip его содержимого и частичных блоков. Вам придется научиться разбирать их вручную.
основной Http 1.0 прост, протокол хорошо документирован онлайн, наше дружелюбное соседство Google может помочь вам с этим.
Я бы создал прокси-сервер SOCKS, который может туннелировать HTTP, а затем принимать запросы от HttpWebRequest и пересылать их. Я думаю, что это было бы намного проще, чем воссоздавать все, что делает HttpWebRequest. Вы можете начать с Privoxy, или просто свернуть свой собственный. Протокол прост и документирован здесь:
http://en.wikipedia.org/wiki/SOCKS
и на RFC, на которые они ссылаются.
Вы упомянули, что вы должны иметь много разных прокси-серверов-вы можете настроить локальный порт для каждого из них.