Unity引擎源码解析(伪) - 6 UnityWebRequest

文章详细描述了UnityWebRequest在Unity游戏开发中如何通过C#实现GET接口,涉及URL设置、请求方法、下载处理器和上传处理器的管理,以及C++引擎层的底层实现,包括不同平台间的适配和传输工具的选择。

UnityWebRequest 提供了一个模块化系统,用于构成 HTTP 请求和处理 HTTP 响应。主要目标是让 Unity 游戏与 Web 浏览器后端进行交互。

本篇只分析最常用的 Get 接口的实现细节:

UnityWebRequest www = UnityWebRequest.Get("https://www.xxx.com");
yield return www.SendWebRequest();

查找mono绑定类:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using UnityEngineInternal;
using UnityEngine.Bindings;
using UnityEngine.Scripting;

namespace UnityEngine.Networking
{
    [StructLayout(LayoutKind.Sequential)]
    [NativeHeader("Modules/UnityWebRequest/Public/UnityWebRequest.h")]
    public partial class UnityWebRequest : IDisposable
    {
        public static UnityWebRequest Get(string uri)
        {
            UnityWebRequest request = new UnityWebRequest(uri, kHttpVerbGET, new DownloadHandlerBuffer(), null);
            return request;
        }
        //构造方法
        public UnityWebRequest(string url, string method, DownloadHandler downloadHandler, UploadHandler uploadHandler)
        {
            m_Ptr = Create();
            InternalSetDefaults();
            this.url = url;
            this.method = method;
            this.downloadHandler = downloadHandler;
            this.uploadHandler = uploadHandler;
        }
        //C++中实现 1
        internal extern static IntPtr Create();
        //设置url
        public string url
        {
            get
            {
                return GetUrl();
            }
            set
            {
                string localUrl = "http://localhost/";
#if PLATFORM_WEBGL
                localUrl = Application.absoluteURL;
#endif
                InternalSetUrl(WebRequestUtils.MakeInitialUrl(value, localUrl));
            }
        }
        private void InternalSetUrl(string url)
        {
            if (!isModifiable)
                throw new InvalidOperationException("UnityWebRequest has already been sent and its URL cannot be altered");
            UnityWebRequestError ret = SetUrl(url);
            if (ret != UnityWebRequestError.OK)
                throw new InvalidOperationException(UnityWebRequest.GetWebErrorString(ret));
        }
        //C++中实现 2
        private extern UnityWebRequestError SetUrl(string url);
        //设置http的请求方法
        public string method
        {
            get
            {
                ...
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentException("Cannot set a UnityWebRequest's method to an empty or null string");
                }
                switch (value.ToUpper())
                {
                    case kHttpVerbGET:
                        InternalSetMethod(UnityWebRequestMethod.Get);
                        break;
                    case kHttpVerbPOST:
                        InternalSetMethod(UnityWebRequestMethod.Post);
                        break;
                    case kHttpVerbPUT:
                        InternalSetMethod(UnityWebRequestMethod.Put);
                        break;
                    case kHttpVerbHEAD:
                        InternalSetMethod(UnityWebRequestMethod.Head);
                        break;
                    default:
                        InternalSetCustomMethod(value.ToUpper());
                        break;
                }
            }
        }
        internal void InternalSetMethod(UnityWebRequestMethod methodType)
        {
            if (!isModifiable)
                throw new InvalidOperationException("UnityWebRequest has already been sent and its request method can no longer be altered");
            UnityWebRequestError ret = SetMethod(methodType);
            if (ret != UnityWebRequestError.OK)
                throw new InvalidOperationException(UnityWebRequest.GetWebErrorString(ret));
        }
        //C++中实现 3
        private extern UnityWebRequestError SetMethod(UnityWebRequestMethod methodType);
        //设置下载处理器 DownloadHandler
        public DownloadHandler downloadHandler
        {
            get
            {
                return m_DownloadHandler;
            }
            set
            {
                if (!isModifiable)
                    throw new InvalidOperationException("UnityWebRequest has already been sent; cannot modify the download handler");
                UnityWebRequestError ret = SetDownloadHandler(value);
                if (ret != UnityWebRequestError.OK)
                    throw new InvalidOperationException(UnityWebRequest.GetWebErrorString(ret));
                m_DownloadHandler = value;
            }
        }
        //C++中实现 4
        private extern UnityWebRequestError SetDownloadHandler(DownloadHandler dh);
    }
}

可以看到 UnityWebRequest.Get() 这样的一次调用,实际上在C++引擎层做了4个步骤:Create() 创建一个 UnityWebRequest 的实例;SetUrl() 设置url;SetMethod() 设置请求方法类型;SetDownloadHandler()设置下载器。

C++中 UnityWebRequest 的类结构:
class UnityWebRequest : public UnityWebRequestBase;
//上面C#调用的外部函数 Create() 在这里实现
UnityWebRequest* UnityWebRequest::Create()
{
    return UNITY_NEW(UnityWebRequest, kMemWebRequest);
}

//分平台的实现...

template<class TTransport>
class UnityWebRequestDefaultBase : public UnityWebRequestProto<UnityWebRequestTransport, ...>;

template<class TTransport, ...>
class UnityWebRequestProto : public NonCopyable;

继承结构只有三层,重点是其中的泛型 TTransport,这个类型会根据不同的平台发生变化,它表示实际的HTTP传输管道,也就是处理完业务逻辑之后,真正发生数据传输的实现在 TTransport 里面。

看看各个平台的判断处理:

#if PLATFORM_WINRT //winRT 平台
    #include "Platforms/UniversalWindows/Modules/UnityWebRequest/Implementations/WebRequestUAP.h"
    typedef UnityWebRequestUAP UnityWebRequestBase;
#elif UNITYWEBREQUEST_USE_CURL //如果使用curl库
    #if PLATFORM_SWITCH
        #include "Platforms/Switch/Modules/UnityWebRequest/Implementations/WebRequestSwitch.h"
        typedef UnityWebRequestSwitch UnityWebRequestBase;
    #else
        #include "Modules/UnityWebRequest/Implementations/UnityWebRequestCurl.h" //windows 和 安卓
        typedef UnityWebRequestCurl UnityWebRequestBase;
    #endif
#elif UNITYWEBREQUEST_USE_JAVASCRIPT //JavaScript?
    #include "Platforms/WebGL/Modules/UnityWebRequest/Implementations/WebRequestJavaScript.h"
    typedef UnityWebRequestJavaScript UnityWebRequestBase;
#elif PLATFORM_IOS || PLATFORM_TVOS
    #include "Platforms/IOS/Modules/UnityWebRequest/Implementations/WebRequestiPhone.h"
    typedef UnityWebRequestiPhone UnityWebRequestBase;
#elif PLATFORM_PS4
    #include "Platforms/PS4/Modules/UnityWebRequest/Implementations/WebRequestPS4.h"
    typedef UnityWebRequestPS4 UnityWebRequestBase;
#elif PLATFORM_XBOXONE
    #include "Platforms/XBoxOne/Modules/UnityWebRequest/Implementations/WebRequestXboxOne.h"
    typedef UnityWebRequestXboxOne UnityWebRequestBase;
#elif WEBREQUEST_USE_DUMMY || !defined(ENABLE_UNITYWEBREQUEST) || !ENABLE_UNITYWEBREQUEST
    #include "Modules/UnityWebRequest/Dummy/UnityWebRequestDummy.h"
    typedef UnityWebRequestDummy UnityWebRequestBase;
#else
    #error "Unknown/Unsupported type for Web Requests - exclude this module from your source!"
#endif

可以看到对 UnityWebRequestBase 的实现分为了多个平台,我们只看最通用的windows和安卓平台,且使用了curl开源库实现的方式。

class UnityWebRequestCurl : public UnityWebRequestDefaultBase<TransportCurl> //上面泛型TTransport现在对应的是 TransportCurl

class TransportCurl : public UnityWebRequestTransport //TransportCurl 继承 UnityWebRequestTransport

C++中的模板,将泛型传递到 基类 UnityWebRequestProto 中,内部就可以正常使用这个传输工具了。

template<class TTransport, class TAtomicRefCounter, class TRedirectHelper,
         class TResponseHelper,
         class TDownloadHandler,
         class TUploadHandler,
         class TCertificateHandler,
         class THeaderHelper,
         class TAsyncOperation>
class UnityWebRequestProto : public NonCopyable
{
protected:
    TTransport* m_Transport; //多平台的http传输工具
    TDownloadHandler* m_DownloadHandler; //通过SetDownloadHandler()函数,设置进来的字段
    TUploadHandler* m_UploadHandler;
    core::string m_Url; //通过SetUrl()函数,设置进来的字段
    UnityWebRequestMethod m_Method; //通过SetMethod()函数,设置进来的字段
    ...
    WebError SetDownloadHandler(TDownloadHandler* downloadHandler)
    {
        RETURN_IF_UNMODIFIABLE();
        if (m_DownloadHandler == downloadHandler)
            return kWebErrorOK;

        ClearDownloadHandler();
        m_DownloadHandler = downloadHandler;
        if (m_DownloadHandler != NULL)
        {
            m_DownloadHandler->Retain();
        }
        return kWebErrorOK;
    }
    WebError SetUrl(const core::string& newUrl)
    {
        RETURN_IF_UNMODIFIABLE();
        m_Url = newUrl;
        return kWebErrorOK;
    }
    WebError SetMethod(const UnityWebRequestMethod newMethod)
    {
        RETURN_IF_UNMODIFIABLE();
        m_CustomMethod.clear();
        m_Method = newMethod;
        return kWebErrorOK;
    }
}

template<class TTransport>
class UnityWebRequestDefaultBase : public UnityWebRequestProto<UnityWebRequestTransport, ...>
{
protected:
    //创建多平台的http传输工具 TTransport
    virtual UnityWebRequestTransport* CreateTransport()
    {
        if (TransportVFS::CanHandleURI(m_Url))
            return UNITY_NEW(TransportVFS, kMemWebRequest)();
        
        return UNITY_NEW(TTransport, kMemWebRequest)();
    }
};

目前将引擎中C++的实现 UnityWebRequest 大致梳理了一遍,其中的继承结构,以及重点关注的不同平台的传输工具 “TTransport”。

SendWebRequest() 成员函数内部解析:

在C#代码绑定中找到:

namespace UnityEngine.Networking
{
    [StructLayout(LayoutKind.Sequential)]
    [NativeHeader("Modules/UnityWebRequest/Public/UnityWebRequest.h")]
    public partial class UnityWebRequest : IDisposable
    {
        public UnityWebRequestAsyncOperation SendWebRequest()
        {
            UnityWebRequestAsyncOperation webOp = BeginWebRequest();
            if (webOp != null)
                webOp.webRequest = this;
            return webOp;
        }
        
        [NativeThrows]
        internal extern UnityWebRequestAsyncOperation BeginWebRequest();
    }
}

内部调到了C++中的 BeginWebRequest() 函数。

ScriptingObjectPtr UnityWebRequest::BeginWebRequest(ScriptingExceptionPtr* exception)
{
    //判断一下是否初始化
    if (!IsModifiable())
    {
        *exception = Scripting::CreateInvalidOperationException("UnityWebRequest has already been sent; cannot begin sending the request again");
        return SCRIPTING_NULL;
    }
    //创建一个异步操作对象,其实它就是C#中那个 UnityWebRequestAsyncOperation 对应的C++实现
    UnityWebRequestAsyncOperation* operation = UNITY_NEW(UnityWebRequestAsyncOperation, kMemWebRequest)(kMemWebRequest, this); // Create: refcount == 1
    SetAsyncOperation(operation); //将该异步操作对象保存到内部的字段中
    WebError err = Begin(); //开始传输 调转...
    if (err != kWebErrorOK && err != kWebErrorOKCached) //判断是否开启传输失败
    {
        operation->Release();
        operation = NULL;
        if (err != kWebErrorAborted)
            *exception = Scripting::CreateInvalidOperationException("%s", GetWebErrorString(err));
        return SCRIPTING_NULL;
    }
    //将该 异步操作对象 包装一下,传递到mone中(即 C#脚本里)
    ScriptingObjectPtr scriptingObject = scripting_object_new(GetUnityWebRequestScriptingClasses().unityWebRequestAsyncOperation);
    ScriptingObjectWithIntPtrField<UnityWebRequestAsyncOperation>(scriptingObject).SetPtr(operation);
    if (operation != NULL)
        operation->SetCachedScriptingObject(scriptingObject);
    return scriptingObject;
}

WebError UnityWebRequest::Begin()
{
    __FAKEABLE_METHOD__(UnityWebRequest, Begin, ());
    return UnityWebRequestBase::Begin();
}

这里的启动传输核心是在 Begin() 函数中,而且在父类里实现的。

virtual WebError UnityWebRequestProto::Begin()
{
    //内部维护了一个状态。kUnityWebRequestUnsent 表示初始状态
    if (m_Status != kUnityWebRequestUnsent)
    {
        return kWebErrorAlreadySent;
    }
    ...
    //这里是设置了三个阶段的处理函数
    m_TaskQueue.push_back(Task_FinalizeRequest); //最后阶段
    m_TaskQueue.push_back(Task_DoRequest); //第二阶段
    m_TaskQueue.push_back(Task_PrepareRequest); //第一阶段
    
    m_Status = kUnityWebRequestActive; //设置状态:已激活
    Retain();
    //将函数Job_ExecuteUnityWebRequest 传递到异步任务队列,即 放到子线程运行
    GetBackgroundJobQueue().ScheduleJob(Job_ExecuteUnityWebRequest, this);
    return kWebErrorOK;
}
//执行队列中的函数
static void UnityWebRequestProto::Job_ExecuteUnityWebRequest(UnityWebRequestProto* unityWebRequest)
{
    AssertMsg(unityWebRequest != NULL, "Cannot execute a null UnityWebRequest in the job queue");
    {
#if SUPPORT_THREADS
        Mutex::AutoLock lock(unityWebRequest->m_TaskExecMutex);
#endif
        AssertMsg(unityWebRequest->m_TaskQueue.size() > 0, "Cannot execute UnityWebRequest without tasks");
        do
        {
            //从队列后面取一个
            TaskFunc task = unityWebRequest->m_TaskQueue.back();
            unityWebRequest->m_TaskQueue.pop_back();
            if (task != NULL)
                task(unityWebRequest); //执行
            else
                return; //暂时中断
        }
        while (!unityWebRequest->m_TaskQueue.empty());
    }
    //执行完毕,释放
    unityWebRequest->Release();
}

可以看到 Task_PrepareRequestTask_DoRequestTask_FinalizeRequest 这三个函数会在子线程中依次执行,同时还增加了一个小处理:中途如果插入一个NULL的任务,会中断一下。

static void UnityWebRequestProto::Task_PrepareRequest(void* wr)
{
    AssertMsg(wr != NULL, "Cannot execute a null UnityWebRequest in the job queue");
    UnityWebRequestProto* unityWebRequest = (UnityWebRequestProto*)wr;
    unityWebRequest->SetError(unityWebRequest->Prepare());
}
WebError UnityWebRequestProto::Prepare()
{
    ...
    if (m_UploadHandler != NULL) //m_UploadHandler的初始化
    {
        m_UploadHandler->Reset();
        if (GetRequestHeader("Content-Type") == NULL)
        {
            const core::string& contentType = m_UploadHandler->GetContentType();
            if (contentType.empty())
            {
                m_HeaderHelper.SetUnvalidated("Content-Type", "application/octet-stream");
            }
            else
            {
                m_HeaderHelper.SetUnvalidated("Content-Type", contentType);
            }
        }
    }
    ...
    if (m_DownloadHandler != NULL) //m_DownloadHandler的初始化
    {
        WebError errorCode = m_DownloadHandler->Prepare();
        err = GetError();
        if (!IsWebErrorOK(err))
            return err;
        if (!IsWebErrorOK(errorCode) || errorCode == kWebErrorOKCached)
        {
            err = SetError(errorCode);
            return err;
        }
    }
    if (m_Transport == NULL)
    {
        TTransport* transport = CreateTransport(); //创建传输工具,具体实现在类 UnityWebRequestDefaultBase 中
        atomic_store((volatile atomic_word*)&m_Transport, (atomic_word)transport);
    }
    err = GetError();
    if (!IsWebErrorOK(err))
        m_Transport->Abort();
    return m_Transport->Prepare(); //传输工具的初始化
}

准备工作做的事不多,基本都是一些状态初始化,其中重点是创建传输工具TTransport,对本文来说就是创建一个 curl库 的包装工具罢了。

static void UnityWebRequestProto::Task_DoRequest(void* wr)
{
    AssertMsg(wr != NULL, "Cannot execute a null UnityWebRequest in the job queue");
    UnityWebRequestProto* unityWebRequest = (UnityWebRequestProto*)wr;
    if (unityWebRequest->ShouldDoRequest())
    {
        WebError err = unityWebRequest->DoRequest(); //正式执行传输
        //判断是否启用阻塞模式的传输
        bool synchronousTransport = unityWebRequest->m_Transport->IsSynchronous();
        if (synchronousTransport)
            err = unityWebRequest->SetError(err);
        else
            unityWebRequest->SetError(err);
        if (!IsWebErrorOK(err))
        {
            if (!unityWebRequest->m_Responses.empty())
                unityWebRequest->m_Responses.back().SetStatusCode(unityWebRequest->m_Transport->GetStatus());
            return;
        }
        if (synchronousTransport)
            unityWebRequest->SetError(unityWebRequest->PostRequest()); //同步传输完成,PostRequest 是用来检查重定向问题,暂略
        else
            unityWebRequest->m_TaskQueue.push_back((TaskFunc)NULL); //异步传输中断,等待回调函数 FinishDoRequest 的执行
    }
}
WebError UnityWebRequestProto::DoRequest()
{
    ...
    bool useUploadHandler = true;
    if (m_Responses.size() > 0)
    {
        typename ResponseVector::reference previousResponse = m_Responses.back();
        useUploadHandler = !previousResponse.IsRedirect() || previousResponse.GetStatusCode() == 307;
    }
    typename ResponseVector::reference currentResponse = m_Responses.emplace_back();
    //参数设置,准备正式启用传输工具 m_Transport
    TransportDoRequestArgsProto<THeaderHelper, TDownloadHandler, TUploadHandler, TCertificateHandler, TResponseHelper> args;
    args.url = m_Url;
    args.timeoutMsec = m_TimeoutMsec;
    args.method = m_Method;
    args.customMethod = m_CustomMethod;
    args.chunkedTransfer = m_ChunkedTransfer;
    args.use100Continue = m_Use100Continue;
    args.forceDedicatedThread = m_ForceDedicatedThread;
    args.suppressErrorsToConsole = m_SuppressErrorsToConsole;
    args.customHeaders = &m_HeaderHelper;
    args.uploadHandler = useUploadHandler ? m_UploadHandler : NULL;
    args.downloadHandler = m_DownloadHandler; //将之前的 下载处理器 DownloadHandler 传递进去
    args.certificateHandler = m_CertificateHandler;
    args.responseHelper = &currentResponse;
    args.request = (UnityWebRequest*)this;
    return m_Transport->DoRequest(args);
}

正式执行的阶段会判断是同步还是异步,其实不论是或否,对于开发者来说都是异步,因为到这一步已经是在子线程中了。对于异步来说,传输工具完成之后会调用UnityWebRequestProto 中的 FinishDoRequest 函数。

void UnityWebRequestProto::FinishDoRequest(WebError doRequestResult)
{
#if SUPPORT_THREADS
    Mutex::AutoLock lock(m_TaskExecMutex);
#endif
    WebError err = SetError(doRequestResult);
    if (!IsWebErrorOK(err))
    {
        if (!m_Responses.empty())
            m_Responses.back().SetStatusCode(m_Transport->GetStatus());
    }
    else
        SetError(PostRequest());
    //继续执行队列中的函数 Task_FinalizeRequest
    m_AsyncDependency = GetBackgroundJobQueue().ScheduleJob(Job_ExecuteUnityWebRequest, this);
}
static void UnityWebRequestProto::Task_FinalizeRequest(void* wr)
{
    AssertMsg(wr != NULL, "Cannot execute a null UnityWebRequest in the job queue");
    UnityWebRequestProto* unityWebRequest = (UnityWebRequestProto*)wr;
    unityWebRequest->Finalize();
}
void UnityWebRequestProto::Finalize()
{
    ...
    bool dhCompleteOnMain = false;
    if (m_DownloadHandler != NULL)
    {
        ...
        WebError err = GetError();
        if (!IsWebErrorOK(err))
        {
            m_DownloadHandler->OnAbort();
        }
        else
        {
            m_DownloadHandler->OnFinishReceiveData();
            dhCompleteOnMain = m_DownloadHandler->OnCompleteContentRequiresMain();
            //是否要在主线程中调用,主要是看每种 Handler 自己的实现
            if (dhCompleteOnMain)
            {
                //Task_FinalizeAfterDHCompleteContent 中调用的是 FinalizeAfterDHCompleteContent
                m_TaskQueue.push_back(Task_FinalizeAfterDHCompleteContent);
                m_TaskQueue.push_back((TaskFunc)NULL);
                //Job_DownloadHandlerOnCompleteContent 中调用的是 OnCompleteContent
                ScheduleMainThreadJob(Job_DownloadHandlerOnCompleteContent, this);
            }
            else
                m_DownloadHandler->OnCompleteContent(); //内部只是设置一个完成的状态
        }
    }
    if (!dhCompleteOnMain)
        FinalizeAfterDHCompleteContent(); //跳转
}
//最后的异步任务一定得在主线程中执行
void UnityWebRequestProto::FinalizeAfterDHCompleteContent()
{
    ...
    if (m_AsyncOperation != NULL)
    {
        ScheduleMainThreadJob(Job_InvokeCoroutine, m_AsyncOperation);
        m_AsyncOperation = NULL;
    }
}
static void UnityWebRequestProto::Job_InvokeCoroutine(TAsyncOperation* ao)
{
    AssertMsg(ao != NULL, "Cannot invoke a null coroutine");
    ao->InvokeCoroutine();
    ao->Release();
}

最后阶段的重点是:调用 DownloadHandler OnCompleteContent() 函数,和异步操作对象 UnityWebRequestAsyncOperation 中的 InvokeCoroutine() 函数。

接下来提两点:ScheduleMainThreadJob 函数 和 协程中的处理。
template<typename T>
static void ScheduleMainThreadJob(void func(T*), T* userData)
{
    GetBackgroundJobQueue().ScheduleMainThreadJob(func, userData);
    ..
}

BackgroundJobQueue 是Unity内部一个多任务通用工具,既有线程池的作用,也有子线程同步主线程的功能,具体实现略。看下子线程传递主线程的循环阶段:

REGISTER_PLAYERLOOP_CALL(EarlyUpdate, ExecuteMainThreadJobs,
{
    GetBackgroundJobQueue().ExecuteMainThreadJobs();
});

可以看到是在 EarlyUpdate 的循环阶段中,这是早于 Update 的。

回顾之前的协程处理

if ((scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().asyncOperation)) && (async = ScriptingObjectWithIntPtrField<AsyncOperation>(monoWait).GetPtr()) != NULL)
{
    ...
    async->SetCoroutineCallback(ContinueCoroutine, m_Behaviour, this, CleanupCoroutine);
    ...
}

异步对象 UnityWebRequestAsyncOperation 调用的 InvokeCoroutine() 函数中,实际调用的就是在协程中 SetCoroutineCallback() 传入的 ContinueCoroutine函数指针。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星痕无量

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值