HttpWebRequest.GetResponse() 一直超时

31

我编写了一个简单的C#函数,通过以下API调用从MtGox获取交易历史:

https://data.mtgox.com/api/1/BTCUSD/trades?since=<trade_id>

这里记录了详细信息:https://en.bitcoin.it/wiki/MtGox/API/HTTP/v1#Multi_currency_trades

以下是函数:

string GetTradesOnline(Int64 tid)
{
    Thread.Sleep(30000);

    // communicate
    string url = "https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString();
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    StreamReader reader = new StreamReader(response.GetResponseStream());

    string json = reader.ReadToEnd();
    reader.Close();
    reader.Dispose();
    response.Close();

    return json;
}

我从 tid=0 (交易 ID) 开始获取数据(从一开始)。对于每个请求,我会收到包含 1000 条交易详情的响应。我始终将上一个响应中的交易 ID 发送给下一个请求。这对于恰好四个请求和响应正常工作。但是之后,下面这行代码会抛出一个“System.Net.WebException”,显示“操作已超时”:

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

以下是事实:

  • 捕获异常并重新尝试会导致相同的异常
  • 默认的HttpWebRequest.Timeout和.ReadWriteTimeout已经足够高(超过一分钟)
  • 将HttpWebRequest.KeepAlive更改为false也没有解决任何问题
  • 在浏览器中似乎总是工作正常,即使函数失败了
  • https://www.google.com检索响应没有任何问题
  • 成功响应的数量在异常之前每天都不同(但浏览器始终有效)
  • 从上次失败的交易ID开始会立即引发异常
  • 在主线程中调用此函数仍会导致异常
  • 在不同的机器上运行无效
  • 从不同的IP运行也无效
  • 在请求之间增加Thread.Sleep时间不会有帮助

有什么想法是可能出了什么问题?

5个回答

46

我有同样的问题。对我来说,解决方法很简单,只需将 HttpWebResponse 代码包装在 using 块中。

using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
    // Do your processings here....
}

详情:通常当向同一主机发出多个请求时,如果WebResponse未被正确释放,就会出现此问题。这就是using块的作用,可以正确地释放WebResponse对象,从而解决该问题。


1
你能解释一下为什么将这个代码块包装在 using 块中会改变行为吗? - Russell McDonnell
1
嗨@RussellMcDonnell。如果您向同一主机发出多个请求并且未正确处理WebResponse,则通常会出现此问题。这就是为什么使用块在这里成为救星的原因。 - Habeeb
1
各位,你们说得对,我忘记了释放我的响应。当时我应该使用using语句(但我不知道)。然而,由于MtGox已经消失了,我无法测试这是否有所帮助。但这很可能是解决方案。 - symbiont
1
即使这是一篇旧帖,也应该将其标记为正确答案。 - Kodaloid
1
解决了我的问题,这比被采纳的答案更好。我只是认为我不需要处理,因为Web请求本身没有实现IDisposable,我只是没有看响应 :) - OregonGhost
1
太好了,我已经尝试了很多方法来解决它,但这个方法永久地解决了它!谢谢! - Hossein POURAKBAR

23

有两种超时。客户端超时和服务器超时。你试过像这样做吗:

request.Timeout = Timeout.Infinite;
request.KeepAlive = true;

试试像这样...


5
是的,小心处理大量请求,因为服务器会识别这种情况并关闭流量,这通常是阻止服务攻击的方式,会暂时封锁您的IP以防止黑客攻击。如果您同意,请点击接受按钮。 - kevin c

10

我刚刚尝试通过SSL调用LINUX服务器上的REST服务时遇到了类似的问题。在尝试了许多不同的配置方案后,我发现我必须在HTTP头中发送一个UserAgent。

这是我的最终调用REST API的方法。

        private static string RunWebRequest(string url, string json)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        // Header
        request.ContentType = "application/json";
        request.Method = "POST";
        request.AllowAutoRedirect = false;
        request.KeepAlive = false;
        request.Timeout = 30000;
        request.ReadWriteTimeout = 30000;
        request.UserAgent = "test.net";
        request.Accept = "application/json";
        request.ProtocolVersion = HttpVersion.Version11;
        request.Headers.Add("Accept-Language","de_DE");
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        byte[] bytes = Encoding.UTF8.GetBytes(json);
        request.ContentLength = bytes.Length;
        using (var writer = request.GetRequestStream())
        {
            writer.Write(bytes, 0, bytes.Length);
            writer.Flush();
            writer.Close();
        }

        var httpResponse = (HttpWebResponse)request.GetResponse();
        using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
        {
            var jsonReturn = streamReader.ReadToEnd();
            return jsonReturn;
        }
    }

KeepAlive = false;? - PreguntonCojoneroCabrón
我过去在使用 keepAlive=true 的服务方面有过一些糟糕的经历,尤其是在与 SAP 后端交互时。因此,从那以后我将 keepAlive=false 设置为默认值。虽然这样会导致丢失 TCP 套接字,但我们可以确信请求已在双方终止。 - Karl
我的问题是没有关闭写入流。谢谢。 - Guy Lowe

2

这不是一个解决方案,而只是一种替代方法: 现在我几乎只使用WebClient而不是HttpWebRequest。特别是WebClient.UploadString用于POST和PUT以及WebClient.DownloadString。它们只接受和返回字符串。这样我就不必处理流对象,除非我遇到WebException异常。如果需要,我也可以使用WebClient.Headers["Content-type"]设置内容类型。using语句还通过为我调用Dispose使生活更加轻松。

很少情况下为了性能,我将System.Net.ServicePointManager.DefaultConnectionLimit设置得很高,而是使用HttpClient的异步方法进行同时调用。

这是我现在的做法

string GetTradesOnline(Int64 tid)
{
    using (var wc = new WebClient())
    {
        return wc.DownloadString("https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString());
    }
}

另外两个POST示例

// POST
string SubmitData(string data)
{
    string response;
    using (var wc = new WebClient())
    {
        wc.Headers["Content-type"] = "text/plain";
        response = wc.UploadString("https://data.mtgox.com/api/1/BTCUSD/trades", "POST", data);
    }

    return response;
}

// POST: easily url encode multiple parameters
string SubmitForm(string project, string subject, string sender, string message)
{
    // url encoded query
    NameValueCollection query = HttpUtility.ParseQueryString(string.Empty);
    query.Add("project", project);
    query.Add("subject", subject);

    // url encoded data
    NameValueCollection data = HttpUtility.ParseQueryString(string.Empty);
    data.Add("sender", sender);
    data.Add("message", message);

    string response;
    using (var wc = new WebClient())
    {
        wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
        response = wc.UploadString( "https://data.mtgox.com/api/1/BTCUSD/trades?"+query.ToString()
                                  , WebRequestMethods.Http.Post
                                  , data.ToString()
                                  );
    }

    return response;
}

错误处理

try
{
    Console.WriteLine(GetTradesOnline(0));

    string data = File.ReadAllText(@"C:\mydata.txt");
    Console.WriteLine(SubmitData(data));

    Console.WriteLine(SubmitForm("The Big Project", "Progress", "John Smith", "almost done"));
}
catch (WebException ex)
{
    string msg;
    if (ex.Response != null)
    {
        // read response HTTP body
        using (var sr = new StreamReader(ex.Response.GetResponseStream())) msg = sr.ReadToEnd();
    }
    else
    {
        msg = ex.Message;
    }

    Log(msg);
}

1
就我所知,每次使用时我都遇到了超时的问题,即使我的调用已经成功传递给了服务器。在我的情况下,问题是我将Expect设置为application/json,但服务器返回的不是这个格式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接