在Unity iOS上进行HTTP请求的方法?

21
我需要使用所有标准的RESTful方法发送HTTP请求,并访问请求体以便与JSON进行发送/接收。我已经研究了以下内容,

WebRequest.HttpWebRequest

这个方法几乎完美地工作,但有些情况下会出现问题,例如服务器宕机时,GetResponse函数可能需要几秒钟才能返回结果(因为它是同步方法),在这段时间内冻结应用程序。此方法的异步版本BeginGetResponse,在Unity中似乎仍然会冻结应用程序。

UnityEngine.WWW#

出于某种原因,该方法只支持POST和GET请求。但我还需要PUT和DELETE(标准的RESTful方法),因此没有继续研究它。

System.Threading

为了在不冻结应用程序的情况下运行WebRequest.HttpWebRequest.GetResponse方法,我尝试使用线程。线程在编辑器中似乎可以工作(但非常不稳定-如果在应用程序退出时不停止线程,则即使在停止它后,它也会在编辑器中永远运行),并且当构建到iOS设备时,尝试启动线程会导致应用程序崩溃(我忘记记录错误,现在无法访问)。

在具有桥接到Unity应用程序的本机iOS应用程序中运行线程

荒谬,不会尝试。

UniWeb

这个方法。我想知道他们是如何做到的。

以下是我正在尝试的WebRequest.BeginGetResponse方法的示例:

// The RequestState class passes data across async calls.
public class RequestState
{
   const int BufferSize = 1024;
   public StringBuilder RequestData;
   public byte[] BufferRead;
   public WebRequest Request;
   public Stream ResponseStream;
   // Create Decoder for appropriate enconding type.
   public Decoder StreamDecode = Encoding.UTF8.GetDecoder();

   public RequestState()
   {
      BufferRead = new byte[BufferSize];
      RequestData = new StringBuilder(String.Empty);
      Request = null;
      ResponseStream = null;
   }     
}

public class WebRequester
{
    private void ExecuteRequest()
    {
        RequestState requestState = new RequestState();
        WebRequest request = WebRequest.Create("mysite");
        request.BeginGetResponse(new AsyncCallback(Callback), requestState);
    }

    private void Callback(IAsyncResult ar)
    {
      // Get the RequestState object from the async result.
      RequestState rs = (RequestState) ar.AsyncState;

      // Get the WebRequest from RequestState.
      WebRequest req = rs.Request;

      // Call EndGetResponse, which produces the WebResponse object
      //  that came from the request issued above.
      WebResponse resp = req.EndGetResponse(ar);
    }
}

...基于这个:http://msdn.microsoft.com/en-us/library/86wf6409(v=vs.71).aspx


1
请展示一下您如何使用BeginGetResponse的例子...可能是您的代码有问题(一般性评论,不确定Unity对此调用是否有特殊行为)。 - Alexei Levenkov
例子:Unity使用定制版本的Mono 2.6。 - fordeka
先生,您的问题显然非常令人困惑,只有您自己才能回答,但我也遇到了与Unity和HTTP请求完全相同的问题。我正在尝试使用“协程”而不是线程。我认为线程不是最佳选择,但我还没有完全弄清楚如何使用协程解决它。您有任何更新吗?您尝试过我的方法吗?您能分享您的解决方案吗? - cregox
当在协程中使用时,有问题的方法仍会冻结应用程序,直到它返回-我的理论是该方法会冻结调用它的线程。我认为协程不涉及线程,因此在其中调用的任何内容都将在主线程上执行,就像在任何其他地方调用它一样,会冻结应用程序。在线程中调用该方法工作正常,只要非常小心地手动关闭您的线程,因为Unity/Mono似乎不会自动完成这项工作。 - fordeka
协程仍然在调用它们的同一线程上运行。因此,如果您的协程中的代码是阻塞的,它将阻塞您的应用程序。例如,LoadLevelAsync可以工作,因为它是一个异步方法。协程通过在您从其中退出时保存其调用堆栈来工作。当您重新进入协程方法时,它会在离开时恢复执行,保持所有本地堆栈状态。但它仍然在同一个线程上。这就像用户控制的线程调度。这与实际系统线程非常不同。 - juanpaco
显示剩余2条评论
4个回答

11

好的,我最终成功编写了自己的解决方案。我们基本上需要一个请求状态(RequestState)、一个回调方法(Callback Method)和一个超时线程(TimeOut Thread)。这里我将仅复制在UnifyCommunity中完成的工作(现在称为unity3d wiki)。这是过时的代码,但比现有代码更小,因此更方便在此展示。现在我已经在unit3d wiki中删除了System.Actionstatic,以提高性能和简单性:

用法

static public ThisClass Instance;
void Awake () {
    Instance = GetComponent<ThisClass>();
}
static private IEnumerator CheckAvailabilityNow () {
    bool foundURL;
    string checkThisURL = "http://www.example.com/index.html";
    yield return Instance.StartCoroutine(
        WebAsync.CheckForMissingURL(checkThisURL, value => foundURL = !value)
        );
    Debug.Log("Does "+ checkThisURL +" exist? "+ foundURL);
}

WebAsync.cs

using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Collections;
using UnityEngine;

/// <summary>
///  The RequestState class passes data across async calls.
/// </summary>
public class RequestState
{
    public WebRequest webRequest;
    public string errorMessage;

    public RequestState ()
    {
        webRequest = null;
        errorMessage = null;
    }
}

public class WebAsync {
    const int TIMEOUT = 10; // seconds

    /// <summary>
    /// If the URLs returns 404 or connection is broken, it's missing. Else, we suppose it's fine.
    /// </summary>
    /// <param name='url'>
    /// A fully formated URL.
    /// </param>
    /// <param name='result'>
    /// This will bring 'true' if 404 or connection broken and 'false' for everything else.
    /// Use it as this, where "value" is a System sintaxe:
    /// value => your-bool-var = value
    /// </param>
    static public IEnumerator CheckForMissingURL (string url, System.Action<bool> result) {
        result(false);

        Uri httpSite = new Uri(url);
        WebRequest webRequest = WebRequest.Create(httpSite);

        // We need no more than HTTP's head
        webRequest.Method = "HEAD";
        RequestState requestState = new RequestState();

        // Put the request into the state object so it can be passed around
        requestState.webRequest = webRequest;

        // Do the actual async call here
        IAsyncResult asyncResult = (IAsyncResult) webRequest.BeginGetResponse(
            new AsyncCallback(RespCallback), requestState);

        // WebRequest timeout won't work in async calls, so we need this instead
        ThreadPool.RegisterWaitForSingleObject(
            asyncResult.AsyncWaitHandle,
            new WaitOrTimerCallback(ScanTimeoutCallback),
            requestState,
            (TIMEOUT *1000), // obviously because this is in miliseconds
            true
            );

        // Wait until the the call is completed
        while (!asyncResult.IsCompleted) { yield return null; }

        // Deal up with the results
        if (requestState.errorMessage != null) {
            if ( requestState.errorMessage.Contains("404") || requestState.errorMessage.Contains("NameResolutionFailure") ) {
                result(true);
            } else {
                Debug.LogWarning("[WebAsync] Error trying to verify if URL '"+ url +"' exists: "+ requestState.errorMessage);
            }
        }
    }

    static private void RespCallback (IAsyncResult asyncResult) {

        RequestState requestState = (RequestState) asyncResult.AsyncState;
        WebRequest webRequest = requestState.webRequest;

        try {
            webRequest.EndGetResponse(asyncResult);
        } catch (WebException webException) {
            requestState.errorMessage = webException.Message;
        }
    }

    static private void ScanTimeoutCallback (object state, bool timedOut)  { 
        if (timedOut)  {
            RequestState requestState = (RequestState)state;
            if (requestState != null) 
                requestState.webRequest.Abort();
        } else {
            RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)state;
            if (registeredWaitHandle != null)
                registeredWaitHandle.Unregister(null);
        }
    }
}

这在编辑器和独立模式下运行得很好,但是使用 WebRequest 只能在 iOS 上通过完整的 .NET 2.0(而不是子集)工作,并且它似乎禁用了所有/大多数剥离,这使得应用程序对于我的使用来说太大了,令人遗憾。 - Michael
@Michael,是的,我还没有测试过,但这一定是真的。将“API Compatibility Level”设置为**.NET 2.0,将“Stripping Level”设置为Disabled**以确保它能够正常工作。 - cregox
更新:我正在使用带有.NET 2.0子集的WebRequest,在Unity 5.1.2中似乎很好。我在iOS、Android和Windows手机上进行了测试。使用.NET子集还将iOS上的可执行文件大小减少了约5MB,但那是一个调试版本。 - Dev2rights

1

我已经在iOS上成功使用了线程- 我相信由于幽灵线程或其他原因导致了崩溃。重新启动设备似乎修复了崩溃问题,所以我将只使用 WebRequest.HttpWebRequest 与线程。


-1
// javascript in the web player not ios, android or desktop you could just run the following code:

var jscall:String;
    jscall="var reqScript = document.createElement('script');";
    jscall+="reqScript.src = 'synchmanager_secure2.jsp?userid="+uid+"&token="+access_token+"&rnd='+Math.random()*777;";
    jscall+="document.body.appendChild(reqScript);";
Application.ExternalEval(jscall);
// cs
string jscall;
    jscall="var reqScript = document.createElement('script');";
    jscall+="reqScript.src = 'synchmanager_secure2.jsp?userid="+uid+"&token="+access_token+"&rnd='+Math.random()*777;";
    jscall+="document.body.appendChild(reqScript);";
    Application.ExternalEval(jscall);

// then update your object using the your return in a function like this
// json return object always asynch
function sendMyReturn(args){
     var unity=getUnity();
     unity.SendMessage("object", "function", args );
}
sendMyReturn(args);

或者您可以通过预先编写的 AJAX 函数发送它,以便进行安全性目的的自定义标头。 使用此方法,您需要签名标头和来自服务器的签名请求。 相对而言,我更喜欢 md5 签名,因为它们不是那么大。


-1

有一种异步的方法可以做到这一点,而不使用IEnumerator和yield return等内容。请查看eDriven框架。

HttpConnector类:https://github.com/dkozar/eDriven/blob/master/eDriven.Networking/Rpc/Core/HttpConnector.cs

我一直在使用JsonFX和HttpConnector,例如在这个WebPlayer演示中:http://edrivenunity.com/load-images

没有PUT和DELETE并不是一个大问题,因为所有这些都可以使用GET和POST来完成。例如,我正在成功地使用Drupal CMS的REST服务进行通信。


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