Monotouch:iPhone关闭飞行模式后,WebRequest连接失败

3
我的Monotouch应用程序正在与Web服务进行定期后台同步。它运行得很完美,并正确检测到飞行模式。当我关闭WiFi时,它会自动开始使用WWAN(GPRS,3G)连接。到目前为止,我非常满意,但是...在关闭飞行模式后,如果没有WiFi可用,我的应用程序将无法重新连接。
它使用NetworkReachability对象正确检测到WWAN可用并且需要连接。但第一次尝试超时(90秒后,我使用计时器中止运行请求)。当我再试一次时,只要调用EndGetRequestStream,就会出现WebException“错误:连接失败(无法访问主机)”。重新连接的唯一方法是启动另一个应用程序,比如Mail,以建立连接。之后,我的应用程序再次连接无缺陷。或者等待几分钟,直到iPhone进入睡眠状态。唤醒后,连接再次正常建立。
我做错了什么?
以下代码是使用ThreadPool.QueueUserWorkItem(CreateRequest)启动的。
    /// <summary>
    /// Sync step 1: Create the request and start asynchronously sending the data.
    /// </summary>
    private void CreateRequest(object state)
    {
        try
        {
            Console.WriteLine("Phase 1 started...");
            if (!IsNetworkAvailable())
            {
                Ready(SyncState.NoConnection);
                return;
            }
            UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
            _request = (HttpWebRequest)WebRequest.Create(SyncUrl);
            _request.Method = HttpMethodPost;
            _request.ContentType = HttpContentTypeJson;
            Console.WriteLine("Phase 2 is starting...");
            _request.BeginGetRequestStream(new AsyncCallback(StartRequest), null);
        }
        catch (WebException e)
        {
            Console.WriteLine("WebException: " + e.Message + "\r\nStatus: " + e.Status);
            Ready(SyncState.ConnectionError);
        }
    }

    /// <summary>
    /// Sync step 2: Read syncdata from database and send to server.
    /// Start getting the response asynchronously.
    /// </summary>
    private void StartRequest(IAsyncResult asyncResult)
    {
        Console.WriteLine("Phase 2 started...");
        try
        {
            using (var stream = _request.EndGetRequestStream(asyncResult))
            {
                using (var textStream = new StreamWriter(stream))
                {
                    Database.Instance.CreateSyncData().Save(textStream);
                }
            }
            Console.WriteLine("Phase 3 is starting...");
            _request.BeginGetResponse(new AsyncCallback(ProcessResponse), null);
        }
        catch (WebException e)
        {
            Console.WriteLine("WebException: " + e.Message + "\r\nStatus: " + e.Status);
            Ready(SyncState.ConnectionError);
        }
    }

    /// <summary>
    /// Sync step 3: Get the response and process.
    /// </summary>
    private void ProcessResponse(IAsyncResult asyncResult)
    {
        Console.WriteLine("Phase 3 started...");
        try
        {
            using (HttpWebResponse response = (HttpWebResponse)_request.EndGetResponse(asyncResult))
            {
                using (var textStream = new StreamReader(response.GetResponseStream()))
                {
                    var data = (JsonObject)JsonObject.Load(textStream);
                    Database.Instance.ProcessSyncReply(data);
                    Console.WriteLine("Success: " + data.ToString());
                    LastSyncTime = DateTime.Now;
                    Ready(SyncState.Synchronized);
                }
            }
        }
        catch (WebException e)
        {
            Console.WriteLine("WebException: " + e.Message + "\r\nStatus: " + e.Status);
            Ready(SyncState.ConnectionError);
        }
    }

    private bool IsNetworkAvailable(out bool connectionRequired, out bool onlyWWAN)
    {
        bool flagsAvailable;
        NetworkReachabilityFlags networkReachabilityFlags = (NetworkReachabilityFlags)0;
        using (var networkReachability = new NetworkReachability(HostName))
        {
            flagsAvailable = networkReachability.TryGetFlags(out networkReachabilityFlags);
        }
        connectionRequired = 0 != (networkReachabilityFlags & NetworkReachabilityFlags.ConnectionRequired);
        onlyWWAN = 0 != (networkReachabilityFlags & NetworkReachabilityFlags.IsWWAN);
        return flagsAvailable && 0 != (networkReachabilityFlags & NetworkReachabilityFlags.Reachable);
    }

    private bool IsNetworkAvailable()
    {
        bool connectionRequired;
        bool onlyWWAN;
        bool available = IsNetworkAvailable(out connectionRequired, out onlyWWAN);
        string status = "Network status: ";
        if (!available)
            status += "Not available";
        else
        {
            status += "Available; ";
            if (onlyWWAN)
                status += "Mobile; ";
            if (connectionRequired)
                status += "Connection required";
        }
        Console.WriteLine(status);
        return available;
    }
1个回答

2

MonoTouch高级对象(ftp、smtp、http)用于处理网络事务,利用BSD套接字。苹果有一种机制,即使3G/EDGE连接处于“活动”状态,它也会进入睡眠状态。唤醒它的唯一方法是使用CFStream或NSStream资源,没有公开暴露的API来唤醒BSD套接字的GPRS连接。幸运的是,您可以解决这个问题。MonoTouch提供了一个API:

MonoTouch.ObjCRuntime.Runtime.StartWWAN (Uri uri);

此API仅接受HTTP/HTTPS URI,并将快速连接到指定的API,以重新唤醒所有连接的WWAN,在此时,WWAN将保持活动状态,直到再次进入飞行模式或超时。


谢谢Geoff,这很有帮助。然而,第一次大部分时间我仍然会遇到超时问题。在此之后的重试总是能成功连接。当然,我可以把这个作为一种解决方法,但我本来希望第一次尝试就能成功。超时并不是由连接延迟引起的:StartWWAN通常需要9-10秒钟,而我的请求只需要几秒钟。 - Marcel Wolterbeek
StartWWAN 花费很长时间的原因是在唤醒 GRPS 调制解调器时需要一些时间。你应该在子线程上异步执行此操作。 - Geoff Norton
1
实际上,这不是StartWWAN超时(Start WWAN从未超过10秒),而是在此之后开始的请求。我进行了更多的测试。超时仅在关闭飞行模式后发生,正常连接唤醒后永远不会发生超时。我尝试使用iPhone邮件程序,它似乎也有同样的问题,所以我想这与monotouch无关。因此,在请求超时后最好的选择可能是重试(邮件似乎也是这样做的)。 - Marcel Wolterbeek
我即使在WiFi上也遇到了类似的问题,而Runtime.StartWWAN()似乎没有帮助。 - David Moles

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