简体   繁体   English

HttpWebRequest使用分块数据很慢

[英]HttpWebRequest is slow with chunked data

I'm using HttpWebRequest to connect to my in-house built HTTP server. 我正在使用HttpWebRequest连接到我内部构建的HTTP服务器。 My problem is that it is a lot slower than connecting to the server via for instance PostMan ( https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en ), which is probably using the built-in functions in Chrome to request data. 我的问题是,它比通过PostMan( https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en )连接到服务器慢很多,这可能正在使用Chrome中的内置函数可请求数据。

The server is built using this example on MSDN ( http://msdn.microsoft.com/en-us/library/dxkwh6zw.aspx ) and uses a buffer size of 64. The request is a HTTP request with some data in the body. 服务器是在MSDN( http://msdn.microsoft.com/en-us/library/dxkwh6zw.aspx )上使用此示例构建的,并使用64的缓冲区大小。请求是一个HTTP请求,其中包含一些数据。

When connecting via PostMan, the request is split into a bunch of chunks and BeginRecieve() is called multiple times, each time receiving 64B and taking about 2 milliseconds. 当通过PostMan连接时,请求被分成一堆块,并且BeginRecieve()被多次调用,每次接收64B并花费大约2毫秒。 Except the last one, which receives less than 64B. 除了最后一个,收到少于64B。

But when connecting with my client using HttpWebRequest, the first BeginRecieve() callback receives 64B and takes about 1 ms, the following receives only 47B and takes almost 200 ms, and finally the third receives about 58B and takes 2ms. 但是当使用HttpWebRequest与我的客户端连接时,第一个BeginRecieve()回调接收64B并且大约需要1毫秒,以下只接收47B并且需要大约200毫秒,最后第三个接收大约58B并且花费2毫秒。

What is up with the second BeginRecieve? 第二个BeginRecieve有什么用? I note that the connection is established as soon as I start to write data to the HttpWebRequest input stream, but the data reception does not start until I call GetResponse(). 我注意到,一旦我开始将数据写入HttpWebRequest输入流,就建立了连接,但是在我调用GetResponse()之前,数据接收才开始。

Here is my HttpWebRequest code: 这是我的HttpWebRequest代码:

var request = (HttpWebRequest)WebRequest.Create(url);

request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;

if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
    var dataBytes = Encoding.UTF8.GetBytes(data);

    try
    {
        var dataStream = request.GetRequestStream();
        dataStream.Write(dataBytes, 0, dataBytes.Length);
        dataStream.Close();
    }
    catch (Exception ex)
    {
        throw;
    }

}

WebResponse response = null;
try
{
    response = request.GetResponse();
}
catch (Exception ex)
{
    throw;
}

var responseReader = new StreamReader(rStream, Encoding.UTF8);
var responseStr = responseReader.ReadToEnd();

responseReader.Close();
response.Close();

What am I doing wrong? 我究竟做错了什么? Why is it behaving so much differently than a HTTP request from a web browser? 为什么它的行为与来自Web浏览器的HTTP请求的行为有很大不同? This is effectively adding 200ms of lag to my application. 这实际上为我的应用程序增加了200ms的延迟。

This looks like a typical case of the Nagle algorithm clashing with TCP Delayed Acknowledgement . 这看起来像Nagle算法TCP延迟确认冲突的典型情况。 In your case you are sending a small Http Request (~170 bytes according to your numbers). 在你的情况下,你发送一个小的Http请求(根据你的号码约170字节)。 This is likely less than the MSS (Maximum Segment Size) meaning that the Nagle Algorithm will kick in. The server is probably delaying the ACK resulting in a delay of up to 500 ms. 这可能小于MSS(最大段大小),这意味着Nagle算法将启动。服务器可能延迟ACK,导致延迟高达500毫秒。 See links for details. 请参阅链接了解详情

You can disable Nagle via ServicePointManager.UseNagleAlgorithm = false (before issuing the first request), see MSDN . 您可以通过ServicePointManager.UseNagleAlgorithm = false禁用Nagle(在发出第一个请求之前),请参阅MSDN

Also see Nagle's Algorithm is Not Friendly towards Small Requests for a detailed discussion including a Wireshark analysis. 另请参阅Nagle的算法对小请求不友好的详细讨论,包括Wireshark分析。

Note: In your answer you are running into the same situation when you do write-write-read. 注意:在您的回答中,当您执行write-write-read时,您遇到了相同的情况。 When you switch to write-read you overcome this problem. 当您切换到写入读取时,您可以解决此问题。 However I do not believe you can instruct the HttpWebRequest (or HttpClient for that matter) to send small requests as a single TCP write operation. 但是我不相信你可以指示HttpWebRequest(或HttpClient)将小请求作为单个TCP写操作发送。 That would probably be a good optimization in some cases. 在某些情况下,这可能是一个很好的优化。 Althought it may lead to some additional array copying, affecting performance negatively. 虽然它可能会导致一些额外的阵列复制,从而对性能产生负面影响。

200ms is the typical latency of the Nagle algorithm. 200ms是Nagle算法的典型延迟。 This gives rise to the suspicion that the server or the client is using Nagling. 这引起怀疑服务器或客户端正在使用Nagling。 You say you are using a sample from MSDN as the server... Well there you go. 你说你使用MSDN的样本作为服务器......那么你去吧。 Use a proper server or disable Nagling. 使用适当的服务器或禁用Nagling。

Assuming that the built-in HttpWebRequest class has an unnecessary 200ms latency is very unlikely. 假设内置的HttpWebRequest类有不必要的200ms延迟是不太可能的。 Look elsewhere. 看别处。 Look at your code to find the problem. 查看您的代码以找到问题。

It seems like HttpWebRequest is just really slow. 好像HttpWebRequest真的很慢。

Funny thing: I implemented my own HTTP client using Sockets, and I found a clue to why HttpWebRequest is so slow. 有趣的是:我使用套接字实现了自己的HTTP客户端,我发现了为什么HttpWebRequest如此缓慢的线索。 If I encoded my ASCII headers into its own byte array and sent them on the stream, followed by the byte array encoded from my data, my Sockets-based HTTP client behaved exactly like HttpWebRequest: first it fills one buffer with data (part of the header), then it uses another buffer partially (the rest of the header), waits 200 ms and then sends the rest of the data. 如果我将我的ASCII标头编码到它自己的字节数组并在流上发送它们,然后是从我的数据编码的字节数组,我的基于套接字的HTTP客户端就像HttpWebRequest一样:首先它用一个数据填充一个缓冲区(部分标题),然后它部分地使用另一个缓冲区(标题的其余部分),等待200毫秒,然后发送其余的数据。

The code: 编码:

TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();

// Send this out
stream.Write(headerData, 0, headerData.Length);
stream.Write(bodyData, 0, bodyData.Length);
stream.Flush();

The solution was of course to append the two byte arrays before sending them out on the stream. 解决方案当然是在将两个字节数组发送到流上之前附加它们。 My application is now behaving as espected. 我的应用程序现在表现得很好。

The code with a single stream write: 使用单个流写入的代码:

TcpClient client = new TcpClient(server, port);
NetworkStream stream = client.GetStream();

var totalData = new byte[headerBytes.Length + bodyData.Length];
Array.Copy(headerBytes,totalData,headerBytes.Length);
Array.Copy(bodyData,0,totalData,headerBytes.Length,bodyData.Length);

// Send this out
stream.Write(totalData, 0, totalData.Length);
stream.Flush();

And HttpWebRequest seems to send the header before I write to the request stream, so it might be implemented somewhat like my first code sample. HttpWebRequest似乎在我写入请求流之前发送了头文件,所以它可能有点像我的第一个代码示例。 Does this make sense at all? 这有意义吗?

Hope this is helpful for anyone with the same problem! 希望这对有同样问题的人有帮助!

Try this: you need to dispose of your IDisposables: 试试这个:你需要处理你的IDisposables:

var request = (HttpWebRequest)WebRequest.Create(url);

request.Method = verb;
request.Timeout = timeout;
request.Proxy = null;
request.KeepAlive = false;
request.Headers.Add("Content-Encoding", "UTF-8");
System.Net.ServicePointManager.Expect100Continue = false;
request.ServicePoint.Expect100Continue = false;

if ((verb == "POST" || verb == "PUT") && !String.IsNullOrEmpty(data))
{
    var dataBytes = Encoding.UTF8.GetBytes(data);

    using (var dataStream = request.GetRequestStream())
    {
        dataStream.Write(dataBytes, 0, dataBytes.Length);
    }
}

string responseStr;
using (var response = request.GetResponse())
{
    using (var responseReader = new StreamReader(rStream, Encoding.UTF8))
    {
        responseStr = responseReader.ReadToEnd();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM