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