Есть ли методы ускорения получения данных по api?
Есть код для получения данных по api. Время получения данных 1,5 секунды. Ping 3-15 мс. Существуют ли другие фреймворки, которые позволяют получать данные быстрее (именно в один поток)? Или может есть какие-то фишечки, которые позволят получать данные быстрее. Например, использование специальных DNS серверов, какие-либо настройки в интернет экспортере или еще чего-нибудь? Я так понимаю, что основное время тут затрачивается на отправку пакетов, обработку на сервере и получение данных, могу ли я как-то на это повлиять?
private string CallEndpoint(String Method, String EndPoint, Dictionary<object, object> Params = null)
{
HttpWebRequest Request;
string Url = API_URL + EndPoint;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (Params != null && Params.Count > 0)
{
string ParamString = string.Join("&", Params.Select(entry => $"{entry.Key}={entry.Value}"));
if (Method == "POST")
{
Request = (HttpWebRequest)WebRequest.Create(Url);
Request.ServicePoint.ConnectionLimit = 20;
Request.Proxy = null;
Request.Method = Method;
var ByteData = Encoding.ASCII.GetBytes(ParamString);
Request.ContentType = "application/x-www-form-urlencoded";
Request.ContentLength = ByteData.Length;
using (var Stream = Request.GetRequestStream())
{
Stream.Write(ByteData, 0, ByteData.Length);
}
}
else
{
Url = Url + '?' + ParamString;
Request = (HttpWebRequest)WebRequest.Create(Url);
Request.Method = Method;
}
}
else
{
Request = (HttpWebRequest)WebRequest.Create(Url);
Request.Method = Method;
}
HttpWebResponse Response;
var ResponseString = "";
try
{
Stopwatch sw = new Stopwatch();
sw.Start();
Response = (HttpWebResponse)Request.GetResponse();
sw.Stop();
ResponseString = new StreamReader(Response.GetResponseStream()).ReadToEnd();
textBox3.Invoke((ThreadStart)delegate ()
{
textBox3.AppendText("Время запроса: " + sw.ElapsedMilliseconds.ToString() + "\r\n");
});
}
catch (System.Net.WebException ex)
{
Response = (HttpWebResponse)ex.Response;
ResponseString = new StreamReader(Response.GetResponseStream()).ReadToEnd();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return ResponseString;
}
Попробовал переписать на HttpClient. См. код ниже. Время отклика увеличилось в среднем на 50 мс. Я так понимаю что это обертка над HttpWebRequest. Отсюда и увеличение времени.
Может быть есть еще какие-то идеи?
if (method == "POST")
{
Dictionary<string, string> param = new Dictionary<string, string>();
foreach (var req in requestParams)
{
param.Add(req.Key.ToString(), req.Value.ToString());
}
HttpContent ByteData = new FormUrlEncodedContent(param);
ByteData.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = client.PostAsync(url, ByteData).Result;
string responseContent = response.Content.ReadAsStringAsync().Result;
return responseContent;
}
if (method == "GET")
{
if (requestParams?.Count > 0)
{
string paramString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
url += '?' + paramString;
}
HttpResponseMessage response = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result;
string responseContent = response.Content.ReadAsStringAsync().Result;
return responseContent;
}
Мой код, который получился. Убрал даже проверки для улучшения скорости.
// HttpClient создается один раз на все время работы приложения.
private HttpClient client = new HttpClient();
private async Task<string> CallEndpointAsync(string method, string endPoint, Dictionary<object, object> requestParams = null)
{
string url = API_URL + endPoint;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (method == "POST")
{
foreach (var req in requestParams)
{
param.Add(req.Key.ToString(), req.Value.ToString());
}
HttpContent ByteData = new FormUrlEncodedContent(param);
ByteData.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = client.PostAsync(url, ByteData).Result;
string responseContent = response.Content.ReadAsStringAsync().Result;
return responseContent;
}
if (method == "GET")
{
if (requestParams?.Count > 0)
{
string paramString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
url += '?' + paramString;
}
HttpResponseMessage response = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result;
string responseContent = response.Content.ReadAsStringAsync().Result;
return responseContent;
}
else throw new NotImplementedException("Method " + method + " not implemented");
}
Вот такой код получился, только он все равно в вечном ожидании
private HttpClient client = new HttpClient();
private async Task<string> CallEndpointAsync(string method, string endPoint, Dictionary<object, object> requestParams = null)
{
string url = API_URL + endPoint;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
if (method == "POST")
{
//if (requestParams?.Count > 0)
//{
//string ParamString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
//url += '?' + ParamString;
//}
Dictionary<string, string> param = new Dictionary<string, string>();
foreach (var req in requestParams)
{
param.Add(req.Key.ToString(), req.Value.ToString());
}
HttpContent ByteData = new FormUrlEncodedContent(param);
ByteData.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
//Request.ContentType = "application/x-www-form-urlencoded";
//Request.ContentLength = ByteData.Length;
//HttpResponseMessage response = await client.PostAsync(url, ByteData);
//string responseContent = response.Content.ReadAsStringAsync().Result;
//return responseContent;
using (HttpResponseMessage response = await client.PostAsync(url, ByteData))
{
if (response.IsSuccessStatusCode)
{
// запрос успешен
string result = await response.Content.ReadAsStringAsync();
return result;
}
else
{
// сюда попадаем, если HTTP 404, 403, 500 и т.д.
HttpRequestException httpException = new HttpRequestException((int)response.StatusCode + " " + response.ReasonPhrase);
httpException.Data.Add("content-type", response.Content.Headers.ContentType);
httpException.Data.Add("body", await response.Content.ReadAsStringAsync());
throw httpException;
}
}
}
if (method == "GET")
{
if (requestParams?.Count > 0)
{
string paramString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
url += '?' + paramString;
}
HttpResponseMessage response = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result;
string responseContent = response.Content.ReadAsStringAsync().Result;
return responseContent;
}
else throw new NotImplementedException("Method " + method + " not implemented");
}
Ответы (1 шт):
Попробуйте System.Net.Http.HttpClient, он переиспользует уже открытые TCP соединения, и в случае повторного обращения к тому же серверу, запрос должен пролететь моментально. Поэтому обязательно сравните время первого запроса и последующих.
В вашем же примере для каждого запроса устанавливается отдельное соединение, которое инициализируется с тяжелым TLS оверхедом. Да даже незашифрованное соединение устанавливается долго. Как вы и сказали, около секунды.
Вот переписал ваш метод под него, правда только для GET запроса, и быть может не очень аккуратно, но для теста производительности должно сойти.
// HttpClient создается один раз на все время работы приложения.
private HttpClient client = new HttpClient();
private async Task<string> CallEndpointAsync(string method, string endPoint, Dictionary<object, object> requestParams = null)
{
string url = API_URL + endPoint;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (method == "GET")
{
if (requestParams?.Count > 0)
{
string paramString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
url += '?' + paramString;
}
using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
if (response.IsSuccessStatusCode)
{
// запрос успешен
string result = await response.Content.ReadAsStringAsync();
return result;
}
else
{
// сюда попадаем, если HTTP 404, 403, 500 и т.д.
HttpRequestException httpException = new HttpRequestException((int)response.StatusCode + " " + response.ReasonPhrase);
httpException.Data.Add("content-type", response.Content.Headers.ContentType);
httpException.Data.Add("body", await response.Content.ReadAsStringAsync());
throw httpException;
}
}
}
else throw new NotImplementedException("Method " + method + " not implemented");
}
Обработка исключений за пределами метода запроса, вот так можно использовать
try
{
string httpResult = CallEndpointAsync("GET", "https://example.com").GetAwaiter().GetResult();
MessageBox.Show(httpResult, "Success"); // данные получены успешно
}
catch (HttpRequestException ex)
{
if (ex.Data?.Count > 0 && ex.Data.Contains("content-type") && ex.Data.Contains("body")) // 404, 403, 500?
MessageBox.Show(ex.Data["content-type"].ToString() + "\r\n\r\n" + ex.Data["body"].ToString(), ex.Message);
else
MessageBox.Show(ex.Message); // что-то другое произошло при запросе
}
catch (Exception ex)
{
MessageBox.Show(ex.Message); // что-то еще произошло
}
Чтобы убедиться, что я точно не наврал, вот результат тестов GET запроса в JSON API с рабочей нагрузкой, без шифрования, каждый ответ весит ~15кб, время через запятую в миллисекундах, запросы выполняются асинхронно, в 16 потоков, .NET Core 3.1.
Я в Санкт-Петербурге, привожу результаты для 4 идентичных серверов, расположенных в географически разных точках.
Россия
403, 167, 229, 290, 151, 136, 161, 138, 203, 208, 201, 205, 2808, 2811, 3002, 3010, 3005, 204, 3015, 208, 207, 193, 188, 192, 184, 189, 196, 193, 3408, 202, 204, 205, 205, 206, 206, 208, 209, 206, 204, 211, 210, 1448, 207, 220, 212, 190, 191, 185, 188, 192, 182, 191, 179, 195, 194, 190, 186, 194, 194, 190, 199, 196, 207, 298, 212, 215, 209, 211, 207, 215, 215, 185, 182, 188, 189, 203, 201, 193, 195, 194, 202, 199, 192, 199, 197, 201, 194, 197, 198, 207, 207, 208, 216, 213, 202, 208, 209, 295, 189, 190, 190, 190, 189, 185, 190, 99, 97, 100, 110, 107, 109, 114, 112, 104, 103, 103, 104, 101, 101, 101, 94, 95, 104, 104, 111, 110, 106, 108, 112, 113, 106, 100, 104, 102, 105, 105, 102, 109, 105, 105, 105, 109, 108, 112, 107, 106, 190, 188, 193, 104, 202, 200, 206, 187, 110, 111, 109, 110, 198, 192, 103, 185, 105, 101, 186, 95, 100, 102, 97, 175, 180, 101, 192, 99, 99, 170, 100, 103, 102, 98, 175, 96, 176, 187, 102, 190, 101, 112, 101, 179, 171, 175, 101, 101, 103, 103, 175, 96, 182, 97, 103, 101, 180, 102, 179, 97, 183, 114, 113, 220, 176, 100, 113, 106, 104, 111, 189, 191, 108, 184, 110, 109, 107, 109, 195, 106, 112, 197, 195, 112, 112, 113, 104, 193, 103, 194, 103, 103, 108, 102, 104, 180, 188, 107, 107, 107, 105, 101, 115, 114, 111, 113, 119, 108, 196, 189, 102, 101, 196, 103, 120, 119, 107, 108, 104, 111, 117, 103, 196, 113, 199, 117, 106, 107, 106, 144, 144, 141, 137, 135, 216, 131, 138, 134, 149, 146, 147, 151, 140, 224, 113, 106, 108, 110, 98, 107, 105, 197, 318, 106, 177, 178, 100, 100, 101, 98, 182
total time: 8337
total queries: 307
Германия
112, 143, 197, 503, 556, 547, 523, 552, 529, 555, 529, 663, 628, 656, 227, 671, 653, 661, 137, 648, 204, 202, 207, 203, 202, 205, 90, 87, 205, 87, 92, 95, 94, 91, 98, 111, 82, 84, 91, 99, 98, 108, 98, 95, 110, 99, 98, 93, 92, 104, 109, 113, 179, 149, 161, 153, 146, 175, 155, 150, 145, 144, 209, 188, 194, 215, 192, 196, 81, 82, 89, 209, 205, 200, 206, 141, 201, 144, 147, 202, 139, 205, 207, 204, 209, 208, 205, 87, 83, 88, 86, 209, 203, 212, 217, 149, 208, 146, 146, 148, 149, 205, 216, 201, 199, 210, 206, 82, 81, 194, 201, 209, 144, 196, 146, 153, 190, 152, 201, 199, 194, 221, 206, 196, 198, 85, 211, 199, 207, 199, 192, 201, 202, 197, 199, 154, 150, 199, 195, 193, 182, 199, 79, 85, 82, 93, 91, 100, 93, 83, 149, 86, 165, 157, 83, 89, 165, 166, 163, 162, 165, 91, 83, 257, 250, 186, 183, 176, 146, 146, 148, 206, 86, 87, 85, 205, 205, 209, 205, 206, 140, 149, 201, 194, 80, 98, 92, 89, 91, 202, 203, 145, 146, 186, 215, 203, 202, 92, 197, 88, 89, 95, 132, 136, 191, 195, 202, 197, 211, 90, 92, 211, 218, 134, 140, 132, 192, 199, 204, 198, 207, 201, 88, 86, 90, 82, 87, 90, 84, 96, 105, 95, 95, 99, 184, 180, 165, 167, 161, 166, 171, 165, 169, 82, 83, 209, 211, 199, 205, 211, 212, 207, 161, 202, 79, 206, 203, 199, 196, 151, 196, 200, 201, 153, 87, 84, 86, 203, 207, 196, 198, 201, 204, 157, 264, 267, 268, 273, 276, 213, 281, 92, 216, 276, 206, 211, 210, 203, 206, 200, 155, 154, 200, 211, 200, 196, 209, 203, 197, 88, 102, 89, 81, 89, 86, 88, 213
total time: 4496
total queries: 307
США
238, 339, 235, 233, 236, 281, 232, 233, 239, 232, 287, 240, 3271, 3257, 3260, 3270, 3274, 232, 232, 233, 233, 234, 232, 232, 232, 235, 233, 236, 258, 240, 3949, 3954, 231, 230, 232, 231, 232, 232, 232, 230, 234, 237, 236, 237, 233, 233, 232, 234, 230, 230, 236, 234, 234, 234, 230, 233, 228, 230, 229, 233, 233, 236, 230, 232, 233, 232, 231, 231, 238, 234, 234, 234, 229, 235, 229, 235, 233, 239, 232, 235, 230, 231, 230, 232, 234, 237, 232, 232, 233, 234, 232, 232, 918, 908, 923, 915, 902, 921, 942, 934, 234, 235, 239, 234, 242, 231, 235, 233, 231, 239, 237, 236, 235, 239, 238, 238, 240, 237, 242, 237, 242, 245, 244, 238, 237, 233, 237, 234, 235, 231, 235, 236, 234, 234, 232, 234, 235, 235, 235, 234, 236, 235, 238, 235, 235, 236, 233, 233, 452, 236, 453, 237, 460, 239, 237, 449, 451, 461, 459, 460, 234, 451, 231, 459, 459, 236, 231, 235, 239, 236, 237, 236, 235, 235, 237, 242, 329, 319, 311, 313, 307, 319, 323, 310, 309, 319, 314, 323, 327, 324, 332, 331, 241, 239, 237, 239, 236, 241, 249, 239, 238, 250, 249, 230, 232, 236, 237, 247, 235, 240, 340, 349, 349, 347, 345, 330, 343, 343, 338, 232, 234, 357, 361, 373, 250, 242, 383, 384, 241, 346, 347, 342, 351, 313, 365, 359, 318, 345, 363, 342, 349, 356, 352, 349, 246, 239, 360, 252, 244, 244, 246, 241, 249, 241, 246, 250, 252, 256, 259, 253, 255, 255, 252, 260, 259, 255, 258, 264, 279, 338, 331, 323, 318, 317, 316, 312, 302, 297, 323, 310, 289, 302, 310, 292, 319, 245, 248, 250, 243, 256, 252, 246, 245, 250, 244, 246, 233, 247, 236, 243, 246, 236, 235, 233, 241, 243, 242
total time: 9484
total queries: 307
Сингапур
317, 246, 245, 255, 247, 251, 245, 248, 247, 390, 362, 3143, 3140, 3152, 3163, 3153, 3140, 3281, 239, 243, 363, 362, 361, 364, 357, 243, 238, 357, 3751, 244, 245, 260, 259, 367, 352, 314, 353, 354, 348, 356, 343, 344, 246, 249, 248, 237, 237, 242, 239, 237, 239, 256, 262, 263, 238, 241, 243, 239, 239, 241, 245, 357, 240, 241, 241, 244, 238, 242, 470, 350, 287, 983, 983, 271, 990, 285, 970, 971, 982, 966, 274, 292, 280, 804, 243, 253, 260, 258, 256, 255, 255, 249, 233, 237, 238, 246, 240, 237, 238, 245, 667, 643, 656, 654, 659, 646, 646, 237, 240, 239, 785, 351, 361, 356, 355, 361, 241, 244, 242, 248, 244, 249, 245, 247, 462, 462, 335, 330, 287, 290, 285, 348, 325, 323, 281, 297, 251, 329, 279, 294, 236, 242, 239, 242, 248, 242, 245, 246, 254, 239, 471, 242, 244, 479, 245, 478, 244, 484, 489, 479, 631, 356, 456, 456, 458, 238, 242, 243, 361, 362, 366, 362, 364, 365, 366, 371, 247, 247, 235, 240, 239, 234, 239, 472, 344, 341, 346, 343, 339, 341, 335, 334, 294, 315, 306, 351, 303, 277, 332, 244, 248, 249, 251, 269, 271, 256, 254, 253, 257, 381, 382, 374, 404, 384, 383, 240, 244, 241, 244, 364, 363, 361, 359, 359, 363, 250, 248, 248, 252, 259, 247, 242, 247, 366, 364, 244, 251, 250, 250, 249, 252, 242, 242, 239, 239, 240, 241, 236, 357, 246, 250, 239, 239, 248, 243, 245, 243, 243, 246, 246, 240, 240, 240, 238, 242, 238, 238, 236, 237, 244, 242, 248, 252, 251, 244, 249, 246, 245, 252, 240, 241, 239, 239, 240, 240, 243, 241, 238, 243, 242, 248, 243, 243, 240, 243, 242, 244, 242, 244, 238, 243, 241, 241, 252, 244, 246, 243
total time: 9546
total queries: 307
Все запросы уникальные (кеширование исключено). Можно увидеть некторые запросы, время выполнения в которых ощутимо выше, чем у остальных, в них и создается новое соединение, когда уже открытых не хватает. Общий объем полученных данных 3,5 МБ на каждые 307 запросов.
Вот, дописал ваш метод под C# 8.0, .NET Core
private async Task<string> CallEndpointAsync(string method, string endPoint, Dictionary<object, object> requestParams = null)
{
string url = API_URL + endPoint;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
if (method == "POST")
{
foreach (var req in requestParams)
{
param.Add(req.Key.ToString(), req.Value.ToString());
}
using HttpContent ByteData = new FormUrlEncodedContent(param);
ByteData.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
using HttpResponseMessage response = await client.PostAsync(url, ByteData).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
if (method == "GET")
{
if (requestParams?.Count > 0)
{
string paramString = string.Join("&", requestParams.Select(entry => $"{entry.Key}={entry.Value}"));
url += '?' + paramString;
}
using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
throw new NotImplementedException("Method " + method + " not implemented");
}