【问题标题】:How can I specify a connection-only timeout when executing web requests?执行 Web 请求时如何指定仅连接超时?
【发布时间】:2014-11-16 22:08:03
【问题描述】:

我目前正在使用通过 HttpClient 类发出 HTTP 请求的代码。虽然您可以为请求指定超时时间,但该值适用于整个请求(包括解析主机名、建立连接、发送请求和接收响应)。

如果请求无法解析名称或建立连接,我需要一种方法使请求快速失败,但有时我还需要接收大量数据,因此不能仅仅减少超时。

有没有办法使用内置 (BCL) 类或替代 HTTP 客户端堆栈来实现此目的?

我已经简要地查看了 RestSharp 和 ServiceStack,但似乎都没有为连接部分提供超时(但如果我错了,请纠正我)。

【问题讨论】:

  • 我不确定您是否看过以下内容,如果是,请忽略..HttpWebRequest.Timeout
  • @DJKRAZE HttpWebRequest 的超时“适用于整个请求和响应”(来自 MSDN 页面)。
  • 在建立连接后更改超时值是一个选项吗?
  • 你为什么不事先执行(异步)DNS解析,这是主要的错误?我也不会尝试单独在端口上建立连接——让常规超时来解决这个问题,因为好的防火墙与慢速服务器无法区分——但可以排除 DNS 解析。
  • "如果他们无法解析名称或建立连接" 这有两个部分 A) DNS 解析和 B) 打开到该端口的 Socket。没有 DNS 解析通常是因为 1) DNS 服务器或其其他邻居无法解析名称或 2) DNS 服务器已关闭。第二部分是发送套接字打开请求的愿望,这是端口扫描器所做的,它查看套接字(地址/端口对)是否正在响应。它只需通过 Socket.Open() 启动会话即可。客户端要么响应,要么不响应,如果你有 DNS 解析,它所花费的时间是最短的。

标签: c# httpwebrequest dotnet-httpclient


【解决方案1】:

如果连接时间过长,您可以使用Timer 中止请求。当时间过去时添加一个事件。你可以使用这样的东西:

static WebRequest request;
private static void sendAndReceive()
{
    // The request with a big timeout for receiving large amout of data
    request = HttpWebRequest.Create("http://localhost:8081/index/");
    request.Timeout = 100000;

    // The connection timeout
    var ConnectionTimeoutTime = 100;
    Timer timer = new Timer(ConnectionTimeoutTime);
    timer.Elapsed += connectionTimeout;
    timer.Enabled = true;

    Console.WriteLine("Connecting...");
    try
    {
        using (var stream = request.GetRequestStream())
        {
            Console.WriteLine("Connection success !");
            timer.Enabled = false;

            /*
             *  Sending data ...
             */
            System.Threading.Thread.Sleep(1000000);
        }

        using (var response = (HttpWebResponse)request.GetResponse())
        {
            /*
             *  Receiving datas...
             */
        }
    }
    catch (WebException e)
    {
        if(e.Status==WebExceptionStatus.RequestCanceled) 
            Console.WriteLine("Connection canceled (timeout)");
        else if(e.Status==WebExceptionStatus.ConnectFailure)
            Console.WriteLine("Can't connect to server");
        else if(e.Status==WebExceptionStatus.Timeout)
            Console.WriteLine("Timeout");
        else
            Console.WriteLine("Error");
    }
}

static void connectionTimeout(object sender, System.Timers.ElapsedEventArgs e)
{
    Console.WriteLine("Connection failed...");
    Timer timer = (Timer)sender;
    timer.Enabled = false;

    request.Abort();
}

这里的时间只是举例,您必须根据自己的需要进行调整。

【讨论】:

  • 你是第一个真正解决问题的东西。它不是很漂亮,但仍然很有用!
  • 可以在初始化定时器的时候指定AutoReset = false,去掉handler中的timer.Enabled = false。此外,您可以使 Elapsed 看起来像这样:timer.Elapsed += (o, e) => { try { request?.Abort(); } catch { } }; 内联可以避免全局 request 和额外的处理程序方法。
【解决方案2】:

.NET 的 HttpWebRequest 公开了 2 个属性,用于指定连接远程 HTTP 服务器的超时:

  • Timeout - 获取或设置 GetResponse 和 GetRequestStream 方法的超时值(以毫秒为单位)。
  • ReadWriteTimeout - 写入或读取超时前的毫秒数。默认值为 300,000 毫秒(5 分钟)。

Timeout 属性与您所追求的最接近,但它确实表明无论 Timeout 值如何,DNS 解析最多可能需要 15 秒:

域名系统 (DNS) 查询最多可能需要 15 秒才能返回或超时。如果您的请求包含需要解析的主机名,并且您将 Timeout 设置为小于 15 秒的值,则可能需要 15 秒或更长时间才会引发 WebException 以指示您的请求超时。

为 DNS 查找提前设置低于 15 秒的超时时间的一种方法是使用 lookup the hostname yourself,但许多解决方案需要 P/Invoke 来指定低级设置。

在 ServiceStack HTTP 客户端中指定超时

底层的HttpWebRequest TimeoutReadWriteTimeout 属性也可以在 ServiceStack 的高级 HTTP 客户端中指定,即在 C# Service Clients 中:

var client = new JsonServiceClient(BaseUri) {
    Timeout = TimeSpan.FromSeconds(30)
};

或使用 ServiceStack 的 HTTP Utils 与:

var timeoutMs = 30 * 1000;
var response = url.GetStringFromUrl(requestFilter: req => 
    req.Timeout = timeoutMs);

【讨论】:

  • 感谢您的回复。但是,除非我误解了什么,否则您所做的只是指出 HttpWebRequest 的局限性。我真正在寻找的是没有这些限制的替代品。
  • @MortenMertner 我建议在唯一可用的超时选项中,Timeout 是最接近您正在寻找的选项。此外,所有基于 .NET 底层 HttpWebRequest 构建的客户端也最多只能使用相同的 API,因为更精细控制的唯一解决方法是 P/Invoke 到 Win32 系统 API。
  • 对。我已经在使用 Timeout,但对它不满意,所以仍在寻找替代的 .NET 库。我最终可以自己编写一个(使用系统 API),但这比我能挤进当前周期的工作要多。
  • @MortenMertner 您是否考虑过异步使用 HttpWebRequest 并实现自己的超时逻辑?
【解决方案3】:

我相信 RestSharp 在 RestClient 中确实有超时属性。

        var request = new RestRequest();
        var client = new RestClient
        {
            Timeout = timeout, //Timeout in milliseconds to use for requests made by this client instance
            ReadWriteTimeout = readWriteTimeout //The number of milliseconds before the writing or reading times out.
        };

        var response = client.Execute(request);
        //Handle response

【讨论】:

  • RestSharp 在内部使用 HttpWebRequest,因此 Timeout 的行为与使用 HttpClient 的行为没有什么不同。
  • @MortenMertner,HttpWebRequest 有两种不同的 Timeout,一种 Timeout 用于整个连接生命周期,而 ReadWriteTimeout 用于 Stream 刷新超时。 HttpClient 没有同时指定两者是设计缺陷,但是 HttpClient 是异步的,因此您可以并且您应该实现自己的 Cancellation Token Source 以提供读/写流操作的取消(超时)。
  • @AkashKava 没有实现这两者并不是一个问题(参见这个 SO 问题的第二个答案:stackoverflow.com/questions/1500955/…),而是两者都没有 ConnectionTimeout。我可能会使用计时器并手动中止请求(根据 Ludovic 的建议),但它有异味。
【解决方案4】:

没错,您无法设置此特定超时。 我没有足够的信息来说明这些库是如何构建的,但出于它们的目的,我相信它们是合适的。有人想做一个请求并为所有事情设置一个超时时间。

我建议您采取不同的方法。 你在这里尝试做两件不同的事情,HttpRequest 一次做:

  1. 尝试查找主机/建立连接;
  2. 传输数据;

您可以尝试将其分为两个阶段。

  1. 使用 Ping 类 (check this out) 尝试访问您的主机并为其设置超时;
  2. 使用HttpRequest IF 它可以满足您的需求(超时,

这个过程不应该减慢一切,因为解析名称/路由的一部分将在第一阶段完成。这不会是一次性的。

此解决方案有一个缺点:您的远程主机必须接受 ping。 希望这会有所帮助。

【讨论】:

  • 不要将 PING 用于网络范围的客户端或主机可用性,因为路由器通常不允许 ICMP 数据包。
【解决方案5】:

我用这个方法检查是否可以建立连接。但是,这并不能保证可以通过HttpWebRequest 中的后续调用建立连接。

private static bool CanConnect(string machine)
{
    using (TcpClient client = new TcpClient())
    {
        if (!client.ConnectAsync(machine, 443).Wait(50)) // Check if we can connect in 50ms
        {
            return false;
        }
    }

    return true;
}

【讨论】:

    【解决方案6】:

    如果超时不适合您的需要 - 不要使用它们。您可以使用等待操作完成的处理程序。当您收到响应时 - 停止处理程序并继续。这样,您将在失败时收到短时间请求,并在大量数据时收到长时间请求。

    可能是这样的:

     var handler = new ManualResetEvent(false);
    
     request = (HttpWebRequest)WebRequest.Create(url)
     {
        // initialize parameters such as method
     }
    
     request.BeginGetResponse(new AsyncCallback(delegate(IAsyncResult result)
     {
          try
          {
              var request = (HttpWebRequest)result.AsyncState;
    
              using (var response = (HttpWebResponse)request.EndGetResponse(result))
              {
                  using (var stream = response.GetResponseStream())
                  {
                      // success 
                  }
    
                  response.Close();
              }
          }
          catch (Exception e)
          {
              // fail operations go here
          }
          finally
          {
              handler.Set(); // whenever i succeed or fail
          }
     }), request);
    
     handler.WaitOne(); // wait for the operation to complete
    

    【讨论】:

      【解决方案7】:

      如果一开始只请求标题,然后如果成功,则通常的资源,

      webRequest.Method = "HEAD";
      

      【讨论】:

      • 要了解为什么这不是问题的答案,请考虑请求已经全部是 HEAD 请求的情况。将超时应用于建立连接的原始问题不会消失——它们不会“快速失败”。
      • 我没有看到 OP 是关于所有头部请求的。解决“如果请求无法解析名称或建立连接,我需要一种方法使请求快速失败,但有时我还需要接收大量数据,因此不能仅仅减少超时”的 OP。 Head only 会让您知道您是否有 404、500 或您无法编程的东西。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多