如何使用Socket接收HTTP消息

时间:2010-05-31 19:55:06

标签: c# .net http sockets

我正在为我的网络客户端使用Socket课程。我不能使用HttpWebRequest,因为它不支持socks代理。所以我必须自己解析标题并处理分块编码。对我来说最困难的是确定内容的长度,所以我必须逐字节地读取它。首先,我必须使用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-encoding,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_responseDataMemoryStream)。

9 个答案:

答案 0 :(得分: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)

因此,您可以修改此方法以创建指向socks服务器的Socket,并执行必要的握手以获取外部连接。其余代码可以保持不变。

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

为了确保更改在所有情况下都有效,最好还测试连接何时断开,以确保客户端使用相同的方法重新连接,从而重新建立SOCKS连接并发送SOCKS请求

答案 1 :(得分: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受到欢迎时k将等于0x0d0a0d0a

答案 2 :(得分:1)

在大多数(应该是全部)http请求中,应该有一个名为content-length的标头,它将告诉您请求正文中有多少字节。然后,只需分配适当的字节数并一次读取这些字节即可。

答案 3 :(得分:0)

虽然我倾向于同意mdma尽可能努力避免实现自己的HTTP堆栈,但您可以考虑的一个技巧是从流中等大小的块中读取。如果你执行读操作并给它一个比可用缓冲区大的缓冲区,它应该返回它读取的字节数。这应该可以减少系统调用次数并显着提高性能。但是,您仍然需要像现在一样扫描缓冲区。

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

这里关于扩展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繁重的工作。

答案 6 :(得分: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

答案 7 :(得分:-1)

您可能希望查看TcpClient中的System.Net类,它是Socket的包装器,可简化基本操作。

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

基本的Http 1.0很简单,协议在网上有很好的记录,我们友好的社区谷歌可以帮助你解决这个问题。

答案 8 :(得分:-1)

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

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

在他们链接到的RFC上。

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