Unity中HTTP请求永远无法完成

Unity项目开发笔记(十六)

目前产品在Unity端的开发任务已经趋于平稳 , 但是依旧达不到”非常稳定”的状态 , 随着用户量的增加 , 在复杂的PC网络和设备环境下 每次迭代版本都会频频中坑,这不 这次中招的是WWW和UnityWebRequest.

0x00. 基础介绍

我们知道现在的Unity (Mono2.0 + .NET 3.5) 在不增加第三方插件的情况下 有三种HTTP请求方式:

  • WWW – 大家都知道,对于PC平台来说 底层封装了一遍Curl
  • UnityWebRequest – Unity5.0以后新出来的一种方式
  • HttpWebRequest – .net 自带的一种网络请求方式

以上三种请求方式各有优劣:

WWW 主要是封装得比较便捷 , 下载AssetBoundle Texture等比较方便 省了转换和加载的过程.
UnityWebRequest 主要是更加接近于 HttpWebRequest ,但是参数设置又比 HttpWebRequest 省事一点,适用于项目的大批量API实现 , 使用UnityWebRequest 可以比较自由的添加各种网络数据传输方式和格式.
HttpWebRequest .net框架自带 ,属于三种方式中自由度最大的 , 但是缺点也很明显 各种参数设置必须了然于胸 .

0x01. 问题重现

看下面一段代码

// 创建请求
WWW load = new WWW(url);
float timing = 0;
// 通过 isDone 判断是否结束
while (false == load.isDone)
{
    // 设计超时
    timing = timing + Time.deltaTime;
    if (timing > m_fTimeOut)
    {
        if (onDownloaded != null)
        {
            onDownloaded(null);
        }
        load.Dispose();
        yield break;
    }
    yield return null;
}
if (true == string.IsNullOrEmpty(load.error))
{
    // 请求出错
} 
else
{
    // 请求成功
}

大多数情况下 以上代码是没有问题的,当然在Unity比较老的版本 (4.3以前) 在编辑器模式下是不能通过 isDone 来判断是否结束的. 在极少的情况下 以上参数 isDone 永远都为false , 目前这个BUG触发的概率大概为:

概率 = 1/50 x 1 / 500 : 也就是 500个用户中会出现1个人 他在他的电脑中使用WWW发送50个请求其中有1个请求的isDone永远为false

通过对出现此问题的用户进行分析 ,发现大多数都集中在 联通和移动网络. 地区分布在云南贵州等等.

0x02. 解决方案 (虽然一般都把解决方案放在文末 但是写这篇文章不为分享解决方案)

这个问题的解决方案不值一提 , 直接当使用 WWW 或者 UnityWebRequest 请求超时的时候使用 HttpWebRequest 进行当前请求即可. 对的 就是那么简单,伪代码为:

if(WWW is TimeOut)
{
    GoTo HttpWebRequest;
}
else
{
    Goto Finish;
}

深入WWW底层分析

虽然很轻易解决了问题 , 由于Unity的底层代码不开源 , 我们只能 绕道取荆州, 但是遇到问题绕道一直都不是我的性格 , 现在让我们扒一扒这个WWW底层到底是什么 , 基于某种机缘巧合我手上获得一份Unity老版本的底层C代码 , 同时在这两年Unity官方开源了C#部分代码: 传送门 , 有了这两份东西我们就可以深入WWW看看底层究竟长什么样:

自顶向下 WWW 是这样调用的:

(第一步第二步属于公开代码内容 , 这里以文字呈现 , 后面的深入步骤属于闭源部分 这里只能以图片的方式.)

// 第一步 上层创建
WWW load = new WWW(strURL);

// 第二步 来到UnityEngine.dll
public WWW(string url)
{
    this.InitWWW(url, null, null);
}
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void InitWWW(string url, byte[] postData, string[] iHeaders);

第三步 来到C++底层 – 中间接口

第四步 来到C++底层 – 真正的WWW

这里主要注意 我们不是WebPlayer模式 . 也就是说会走到 CreatePlatformWWWBackend 中

第四步 来到C++底层 – CreatePlatformWWWBackend

一种跟踪 显然会走到 WWWCurl 里 , 注意 Curl,应该是指libcurl – 详解传送门 , 继续跟踪来到如下代码位置:

WWWCurl::WWWCurl -> DoInit -> StartThread -> WWW_ThreadEntryPoint -> GetURL

注意函数 WWW_ThreadEntryPoint 在 www.abortDownload 和 www.GetError() == NULL 都成立的情况下 才 www.FeedUnityWebStream(true) , 在curl整个调用过程中 都是 www.FeedUnityWebStream(false) , 说明isDone就在这个地方被改变.

最后我们看看这个GetURL是什么幺儿~

显然就是HTTP请求头的构造函数了.里面看到很多熟悉的字眼 , 使用fiddler随便抓一个Unity的请求就可以看到上面函数的数据头信息.

WWW底层代码的跟踪到此结束 . 基本上可以断定WWW之所以会发生isDone永远为false 和 curl有很大的关系 .下面我们看看isDone究竟是什么东西:

由上图 WWW 封装了一个 m_UnityWebStream 在 m_UnityWebStream 中保存 isDone数据. 看 IsFinished 函数即可确认:

而 IsFinished 函数里的数据 是根据上面 curl 的 WWWCurl::GetURL 函数中各种回调 WriteCallback,ReadCallback,ProgressCallback,HeaderCallback 中调用 FeedUnityWebStream 去修改的.

根据以上分析 ,如果要得出 isDone为何没有被设置为true, 只需重点跟踪 curl的几个回调函数即可. 根据以上代码 , 接下来准备分析一下libcurl的http请求 , 然后把这个模块单独抽出来 准备写个工具测试一下看看.

今天先到这里.

-EOF-

《Unity中HTTP请求永远无法完成》有2个想法

  1. 我的天,这么绕。自己用CurlSharp封一层就好了~原装无依赖
    话说Unity还真喜欢各种IsReadyToPlay哈= =视频流的IsReadyToPlay就总出问题……

    1. 估计老外从未出现这些问题 , 我发现国内用户PC环境比移动环境复杂多了..最近出问题的用户几乎无一例外的是阉割的Win系统 或者奇葩网络. 而且新增某些第三方库的时候还要避过三大流氓:360/腾讯管家/金山杀毒

发表评论

邮箱地址不会被公开。 必填项已用*标注