2010-05-31 54 views
8

我使用Socket类我的网络客户端接收HTTP消息。我不能使用HttpWebRequest,因为它不支持袜子代理。所以我必须解析头文件并自己处理分块编码。对我来说最困难的是确定内容的长度,所以我必须逐字节读取它。首先,我必须使用ReadByte()来查找最后一个标题(“\ r \ n \ r \ n”组合),然后检查主体是否具有传输编码。如果是这样我要读块的大小等:如何采用Socket

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)。 最好将后连结例如,读取消息主体和正确处理情况下,当:内容已分块编码,是gzip-或放气编码,省略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_responseDataMemoryStream)与已接收到的数据。

+0

如果您只是继续编辑此问题,与你觉得麻烦的东西,我会添加更多简单的答案,我原来的答案。 – 2010-06-08 17:11:50

+0

你不能使用WinInet吗? http://msdn.microsoft.com/en-us/library/aa383996(VS.85).aspx – 2010-06-09 23:10:56

+0

相关,可能重复:http://stackoverflow.com/questions/11862890/c-how-to-execute- a-http-request-using-sockets – vapcguy 2017-05-11 20:16:28

回答

9

我建议你不要这个实现自己 - HTTP 1.1协议十分复杂,使这几个人月的项目。

的问题是,是否有.NET HTTP请求协议解析器?这个问题已经在SO上提出,在答案中你会看到一些建议,包括处理HTTP流的源代码。

Converting Raw HTTP Request into HTTPWebRequest Object

编辑:转子代码是相当复杂的,并且难以阅读/导航作为网页。但是,添加SOCKS支持的实现仍然比自己实现整个HTTP协议要低得多。您可以在几天内获得可以依赖的最佳工作,即基于经过验证的测试实施。

请求和响应从NetworkStream,m_TransportConnection类中读取/写入。这是在这些方法中使用:

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到你的袜子服务器,并做必要的握手来获得外部连接。其余的代码可以保持不变。

我在大约30分钟的时间内在网页上浏览了这些信息。如果将这些文件加载​​到IDE中,这应该会更快。阅读代码看起来像是一种负担 - 毕竟,阅读代码比编写代码难得多,但是对已经建立的工作系统只做了很小的改动。

为了确保这些更改在所有情况下都能正常工作,还应该测试连接何时中断,以确保客户端使用相同方法重新连接,并重新建立SOCKS连接并发送SOCKS请求。

+4

+1不要这样做。 – 2010-06-04 20:35:45

+1

我同意你的看法,但HttpWebRequest(转子)的源代码太复杂了。我什至不能找到实际接收来自网络的数据的功能。 – Poma 2010-06-05 18:47:16

+0

我明白 - 这不是最简单的代码阅读,特别是作为网页。我已经添加了一些指向我的答案。 – mdma 2010-06-05 20:33:06

1

在大多数(应该是所有)的HTTP请求,应该有一个名为标题内容长度,将告诉你多少字节中有请求的主体。然后,只需分配适当的字节数并一次读取这些字节即可。

+2

HTTP 1.1中的某些传输方法不会向您发送有效的内容长度,因为有时会以块的形式发送html。它不是html内容的可靠字段。 – Aren 2010-05-31 20:06:12

+0

无论如何,我必须逐字节读取标题以获得“Content-Length”标题。 – Poma 2010-05-31 20:30:18

+2

而不是逐字节读取应该有一个readLine方法调用,这将允许您一次读取一行。 HTTP协议是... <首行> \ r \ n <可选标题> \ r \ n <可选标题> \ r \ n <...> \ r \ n \ r \ n 因此,您需要阅读一行一行,直到找到内容长度标题。然后你可以将该行分割为“:”来获取标题名称和标题值(长度)。一旦你有了这个长度,一直读直到你到达空行。然后以字节读取您从头文件获得的长度。 你能格式这些评论吗???大声笑 – 2010-05-31 20:53:53

-1

您可能想看中的TcpClient类,它是一个简化基本操作的套接字。

从那里,你将不得不对HTTP协议的阅读起来。也准备做一些压缩操作。 Http 1.1支持GZip的内容和部分块。你将不得不学习很多东西来手工解析它们。

基本Http 1.0很简单,该协议在网上有详细记录,我们的友好邻域Google可以帮助你。

+0

我可以使用'GZipStream'和'DeflateStream'作为 – Poma 2010-05-31 20:29:08

0

尽管我倾向于同意mdma关于尽可能努力避免实现自己的HTTP堆栈,但您可能会考虑的一个技巧是从流中等大小的块中读取数据。如果你做了一个读取,并且给它一个比可用的缓冲区大的缓冲区,它应该返回你读取的字节数。这应该会减少系统调用的数量,并显着提高性能。不过,您仍然必须像现在一样扫描缓冲区。

0

考虑看看另一客户端的代码是有帮助的(如果不是混乱): http://src.chromium.org/viewvc/chrome/trunk/src/net/http/

目前,我正在做这样的事情了。我发现提高客户端效率的最好方法是使用提供的异步套接字函数。它们非常低级,摆脱忙碌的等待并自己处理线程。所有这些在其方法名称中都有BeginEnd。但首先,我会尝试使用阻塞,这样你就可以获得HTTP的语义。那么你可以提高效率。记住:不成熟的优化是邪恶的 - 所以让它工作,然后优化所有的东西!

另外:在使用ToArray()时,您的一些效率可能会受到限制。已知在计算上有点昂贵。一个更好的解决方案可能是将您的中间结果存储在byte[]缓冲区中,并使用正确的编码将它们附加到StringBuilder

对于压缩或缩小的数据,请读入所有数据(请记住,您可能无法在您第一次询问时获得所有数据,请记录您读入的数据量并继续追加到相同的缓冲区)。然后你可以使用GZipStream(..., CompressionMode.Decompress)解码数据。

我想说这样做并不像有些人所暗示的那么困难,你只需要有点冒险吧!

-1

我会创建一个SOCKS代理,可以隧道HTTP,然后让它接受来自HttpWebRequest的请求并转发它们。我认为这比重新创建HttpWebRequest所做的一切要容易得多。你可以从Privoxy开始,或者自己推出。该协议很简单,这里记载:

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

而且在RFC就是他们链接。

你提到你必须有许多不同的代理 - 你可以为每个代理设置一个本地端口。

2

如果问题是ReadByte太慢的瓶颈,我建议你用StreamBuffer包装你的输入流。如果您声称拥有的性能问题由于小读取而造成的成本昂贵,那么这将为您解决问题。

而且,你不需要这样的:

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

HTTP设计要求头只由ASCII字符。你并不想 - 或者需要 - 把它变成实际的.NET字符串(它们是Unicode)。

如果你想找到HTTP头的EOF,你可以做到这一点,以获得良好的性能。

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

当串\r\n\r\n是encoutered k将在这里等于0x0d0a0d0a

+0

虽然这可能有助于解决这个特定的问题,但如果他继续实施HTTP客户端,您不会给他提供他将面临的问题大小的任何指示。持久连接并不是微不足道的,而且不会影响性能。 – mdma 2010-06-09 16:59:14

+0

我相信我们在评论中对您的答案进行了审查。 – 2010-06-10 07:47:03

0

一切答案有关扩展插槽和/或的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

如果可以简单地解决这个问题,那肯定会很好。基础WebHttpRequest.Create如何获得与创建到SocketHttpWebRequest.Create中的SOCKS服务器相同的套接字连接? – mdma 2010-06-09 15:39:18

+1

理论很棒,但我认为你不能这样做。你能发送一个有效的代码示例吗?你如何给HTTPRequest提供TCP连接? AFAIK你不能那样做。 – 2010-06-09 21:22:21

+0

你可以显示'SocksHttpWebRequest'的构造函数吗? – Jaanus 2013-03-02 12:17:00

0

你为什么不读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