问题

在使用 HttpWebRequest 时,同时使用了 5 个线程。 开始时都是使用本地搭建的临时服务器,后来换成了外网服务器,但是下载的文件都较小,在网络条件好的时候很快就会下载完成。 所以大多数情况下都未曾出现问题。

测试代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using UnityEngine;
using System.Net;
using System.Threading;

public class TestConnectionLimit : MonoBehaviour
{
    void Download(object arg)
    {
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        string url = arg as string;

        try
        {
            var request = WebRequest.Create(url);
            request.GetResponse();
        }
        catch (System.Exception e)
        {
            Debug.LogWarning(e.ToString());
        }
        finally
        {
            Debug.Log(url + " elapsedTimeInMilliseconds: " + sw.ElapsedMilliseconds);
        }
    }

    void DownloadAsync(string url)
    {
        var thread = new Thread(new ParameterizedThreadStart(Download));
        thread.Start(url);
    }

    void Start()
    {
        DownloadAsync("http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png");
        DownloadAsync("http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png");
        DownloadAsync("http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png");
        DownloadAsync("http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png");
        DownloadAsync("http://mat1.gtimg.com/www/images/qq2012/qqlogo_1x.png");
    }
}

测试代码地址: C# API WebRequest default connection limit is 2. Use the request.ServicePoint.ConnectionLimit to unlock this limitation

前段时间加入了下载速度限制功能,同时增加了监视调试下载进度界面。 然后发现在限速的情况下:

  • 同一时间只会有两个文件在下载
  • 其他全都是在挂起状态,且过 100 秒后会抛出超时异常

异常信息如下:

1
2
3
4
5
6
System.Net.WebException: The request timed out
  at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0
  at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0
  at TestConnectionLimit.Download (System.Object arg) [0x0001d] in Assets/TestConnectionLimit.cs:18
UnityEngine.Debug:LogWarning(Object)
TestConnectionLimit:Download(Object) (at Assets/TestConnectionLimit.cs:22)

注意:100 秒超时时间是默认时间,可以修改。

环境

  • Unity 5.4.2f2
  • Mac OS X 10.11.4

查找原因

因为使用了 Unity,也就是使用了 Mono,而不是 .Net,所以要搜索的范围大了很多。最终,在一篇博客上 System.Net.WebException when issuing more than two concurrent WebRequest’s – Wade Wegner 找到完全相同的问题,由此确定了问题的原因:HTTP 1.1 中规定了此限制,即连接同一主机的并发连接数不可以超过 2 个。

“Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.”

HTTP/1.1: Connections - Section 8.1.4

分析:由于有以上 2 个并发连接数限制,其他开启的连接会直接等待,直到超时。

解决方法

解决方法最直接的就是提升连接到同一服务器的并发连接数量。

上面博客中提到的提升并发连接数量方法是针对 .NET 应用的,在 Unity 中并无这样的配置文件。同时,我们需要的是动态修改此数量,方便与程序结合动态控制同时下载数量。

在 StackOverflow 上找到了解决方法 .net - How can I programmatically remove the 2 connection limit in WebClient - Stack Overflow

最佳答案的核心代码如下:

1
2
var request = WebRequest.Create(url) as HttpWebRequest;
request.ServicePoint.ConnectionLimit = 10;

必须在创建后动态设置,经测试有效。而全局静态的 System.Net.ServicePointManager.DefaultConnectionLimit = 10 经测试无效。

将上述改动加入到前面的测试代码中,再次运行后发现可以同时建立了 5 个并发请求并成功完成了。