第一次运行时,HttpClient,WebClient或HttpWebRequest花费90秒读取文件

问题描述

我有一个应用程序,该应用程序可以在PC上运行,并且可以从运行在嵌入式设备中的网络服务器上下载文件,该设备通过USB通过RNdis与PC相连。 RNdis连接看起来像一个网络适配器,可以让我在PC和嵌入式设备之间运行TCP / IP连接。

设备通电后,我可以使用网络浏览器在几秒钟内查看设备上的内容。但是,当我的应用程序运行时,对HttpClient的第一次调用需要90秒才能完成。之后。 HttpClient正常运行。如果我关闭我的应用程序并再次打开它,则HttpClient现在可以在第一次尝试时正常工作。应用程序启动后的90秒延迟对于用户来说非常明显。

此问题似乎仅在某些PC上发生,而在其他PC上则没有。我尝试使用HttpClient,WebClient和HttpWebRequest,但结果相同。在阅读了StackOverflow上的其他主题之后,我还尝试了ServicePointManager和ServicePoint中的设置,但是它们没有作用,不会出现此问题。

我可以在控制台应用程序中仅使用以下几行代码来演示该问题:

static async Task Main(string[] args)
{
    using (HttpClient httpClient = new HttpClient())
    {
        string url = "http://169.254.21.151/eventlog/2020-10-11.txt";
        for (; ; )
        {
            DateTime t1 = DateTime.Now;
            try
            {
                HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(url);
                DateTime t2 = DateTime.Now;
                string content = await httpResponseMessage.Content.ReadAsstringAsync();
                DateTime t3 = DateTime.Now;
                TimeSpan dt1 = t2 - t1;
                TimeSpan dt2 = t3 - t2;
                Console.WriteLine($"Http Read {content.Length} bytes after {dt1.TotalMilliseconds:F0} ms + {dt2.TotalMilliseconds:F0} ms");
            }
            catch (HttpRequestException)
            {
                DateTime t2 = DateTime.Now;
                TimeSpan dt = t2 - t1;
                Console.WriteLine($"Http Failed to read after {dt.TotalMilliseconds:F0} ms");
            }
            await Task.Delay(1000);
        }
    }
}

我像这样测试了上面的代码

  1. 启动嵌入式设备
  2. 使用网络浏览器测试HTTP连接并下载文件
  3. 运行上面的代码

结果显示,对httpClient.GetAsync(url)的第一次调用需要90秒才能完成。 90秒让我震惊,因为这是一个可疑的数字,这表明我尚未找到某个超时设置。在多个测试中,测量的90秒延迟非常一致。

Http Read 1106623 bytes after 90449 ms + 0 ms
Http Read 1106623 bytes after 1080 ms + 0 ms
Http Read 1106623 bytes after 1070 ms + 0 ms
Http Read 1106623 bytes after 1110 ms + 0 ms

我缺少某处的设置吗?

解决方法

我找到了蛮力解决方案。我认为问题出在ServicePointManager中,因此我的解决方案是避免在使用TcpClient而不是HttpClient向该硬件发出HTTP请求时使用ServicePointManager,然后自己构建HTTP请求标头。我知道这会因为每个请求创建和销毁套接字而导致性能下降,但是我认为在这种情况下这是可以接受的,因为对设备的请求量很小。我欢迎提出一个更优雅的解决方案的建议。

与问题代码等效的我的代码是:

static void Main(string[] args)
{
    string host = "169.254.21.151";
    string file = "/eventlog/2020-10-11.txt";
    for (; ; )
    {
        DateTime t1 = DateTime.Now;
        try
        {
            string query = $"GET {file} HTTP/1.1\r\nConnection: close\r\nHost: {host}\r\n\r\n";
            byte[] queryBytes = Encoding.UTF8.GetBytes(query);
            using (TcpClient tcpClient = new TcpClient(host,80))
            {
                tcpClient.GetStream().Write(queryBytes,queryBytes.Length);
                using (StreamReader streamReader = new StreamReader(tcpClient.GetStream()))
                {
                    string response = streamReader.ReadToEnd();
                    int index = response.IndexOf("\r\n\r\n");
                    if (index > 0)
                    {
                        string headers = response.Substring(0,index);
                        string content = response.Substring(index + 4);
                        if (headers.Contains("HTTP/1.1 200 OK"))
                        {
                            DateTime t2 = DateTime.Now;
                            TimeSpan dt = t2 - t1;
                            Console.WriteLine($"Http Read {content.Length} bytes after {dt.TotalMilliseconds:F0} ms");
                        }
                    }
                }
            }
        }
        catch (Exception)
        {
            DateTime t2 = DateTime.Now;
            TimeSpan dt = t2 - t1;
            Console.WriteLine($"Http Failed to read after {dt.TotalMilliseconds:F0} ms");
        }
        Thread.Sleep(1000);
    }
}