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_PrepareRequest、Task_DoRequest、Task_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 = ¤tResponse;
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函数指针。
文章详细描述了UnityWebRequest在Unity游戏开发中如何通过C#实现GET接口,涉及URL设置、请求方法、下载处理器和上传处理器的管理,以及C++引擎层的底层实现,包括不同平台间的适配和传输工具的选择。

被折叠的 条评论
为什么被折叠?



