执行 Web 请求时如何指定仅连接超时?

Posted

技术标签:

【中文标题】执行 Web 请求时如何指定仅连接超时?【英文标题】:How can I specify a connection-only timeout when executing web requests? 【发布时间】: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 解析,它所花费的时间是最短的。 【参考方案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 问题的第二个答案:***.com/questions/1500955/…),而是两者都没有 ConnectionTimeout。我可能会使用计时器并手动中止请求(根据 Ludovic 的建议),但它有异味。【参考方案4】:

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

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

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

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

    使用 Ping 类 (check this out) 尝试访问您的主机并为其设置超时; 使用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 或您无法编程的东西。

以上是关于执行 Web 请求时如何指定仅连接超时?的主要内容,如果未能解决你的问题,请参考以下文章

HTTP超时问题

如何解决SSH连接Linux超时自动断开

Linux下CURL设置请求超时时间

适用于 excel 的 BigQuery 连接器 - 请求失败:错误。无法执行查询。获取 URL 时超时

EF 执行查询请求超时,数据多。

java客户端调用webservice时 连接超时知道是网络原因 ,如何重试如果不重试程序就死琐了,