HttpWebRequest работает медленно с кусковыми данными

Я использую HttpWebRequest для подключения к моему встроенному HTTP-серверу. Моя проблема в том, что это намного медленнее, чем подключение к серверу через, например, PostMan ( https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en), который, вероятно, использует встроенные функции в Chrome для запроса данных.

Сервер построен с использованием этого примера на MSDN ( http://msdn.microsoft.com/en-us/library/dxkwh6zw.aspx) и использует размер буфера 64. Запрос представляет собой HTTP-запрос с некоторыми данными в теле,

При подключении через PostMan запрос разбивается на несколько блоков, и BeginRecieve() вызывается несколько раз, каждый раз получая 64B и занимая около 2 миллисекунд. За исключением последнего, который получает менее 64B.

Но при соединении с моим клиентом с использованием HttpWebRequest первый обратный вызов BeginRecieve() получает 64B и занимает около 1 мс, следующий получает только 47B и занимает почти 200 мс, и, наконец, третий получает около 58B и занимает 2 мс.

Что случилось со вторым BeginRecieve? Я отмечаю, что соединение устанавливается, как только я начинаю записывать данные во входной поток HttpWebRequest, но прием данных не начинается, пока я не вызову GetResponse().

Вот мой код 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();

Что я делаю неправильно? Почему он так сильно отличается от HTTP-запроса от веб-браузера? Это эффективно добавляет 200 мс к моему приложению.

4 ответа

Решение

Это похоже на типичный случай столкновения алгоритма Nagle с задержанным подтверждением TCP. В вашем случае вы отправляете небольшой запрос Http (~170 байт в соответствии с вашими номерами). Это, вероятно, меньше, чем MSS (максимальный размер сегмента), что означает, что алгоритм Nagle включится. Сервер, вероятно, задерживает ACK, что приводит к задержке до 500 мс. Смотрите ссылки для деталей.

Вы можете отключить Nagle через ServicePointManager.UseNagleAlgorithm = false (перед отправкой первого запроса), см. MSDN.

Также см . Алгоритм Нэгла "Не дружелюбен к маленьким запросам" для подробного обсуждения, включая анализ Wireshark.

Примечание: в вашем ответе вы сталкиваетесь с той же ситуацией, когда вы делаете запись-запись-чтение. Когда вы переключаетесь на запись-чтение, вы преодолеваете эту проблему. Однако я не верю, что вы можете дать указание HttpWebRequest (или HttpClient в этом отношении) отправлять небольшие запросы в виде одной операции записи TCP. Это, вероятно, будет хорошей оптимизацией в некоторых случаях. Хотя это может привести к дополнительному копированию массива, что отрицательно скажется на производительности.

200 мс - типичная задержка алгоритма Nagle. Это вызывает подозрение, что сервер или клиент использует Nagling. Вы говорите, что используете образец из MSDN в качестве сервера... Ну что ж. Используйте правильный сервер или отключите Nagling.

Предполагая, что встроенный класс HttpWebRequest имеет ненужную задержку в 200 мс, очень маловероятно. Посмотри в другом месте. Посмотрите на свой код, чтобы найти проблему.

Кажется, что HttpWebRequest просто очень медленно.

Забавная вещь: я реализовал свой собственный HTTP-клиент, используя Sockets, и нашел ключ к пониманию того, почему HttpWebRequest такой медленный. Если я закодировал мои ASCII-заголовки в свой собственный байтовый массив и отправил их в поток, а затем байтовый массив, закодированный из моих данных, мой HTTP-клиент на основе сокетов вел себя точно так же, как HttpWebRequest: сначала он заполняет один буфер данными (часть заголовок), затем он частично использует другой буфер (остаток заголовка), ждет 200 мс и затем отправляет оставшиеся данные.

Код:

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();

Конечно, решение заключалось в добавлении двухбайтовых массивов перед их отправкой в ​​поток. Моя заявка сейчас ведет себя как положено.

Код с одним потоком напишите:

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();

И HttpWebRequest, похоже, отправляет заголовок до того, как я запишу в поток запросов, поэтому он может быть реализован как мой первый пример кода. Имеет ли это смысл вообще?

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

Попробуйте это: вам нужно избавиться от ваших 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();
    }
}
Другие вопросы по тегам