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

发布于 2024-10-10 13:23:57 字数 4710 浏览 4 评论 0原文

我的单点触控应用程序正在与网络服务进行定期后台同步。它运行完美并正确检测飞行模式。当我关闭 WiFi 时,它会自动开始使用 WWAN(GPRS、3G)连接。到目前为止,我非常满意,但是...关闭飞行模式后,当没有可用的 WiFi 时,我的应用程序将无法重新连接。

它使用 NetworkReachability 对象正确检测 WWAN 是否可用以及是否需要连接。但第一次尝试超时(90 秒后我使用计时器中止正在运行的请求)。当我再次尝试时,只要调用 EndGetRequestStream,就会收到 WebException“错误:ConnectionFailure(没有到主机的路由)”。再次连接的唯一方法是启动另一个应用程序,例如“邮件”,它会建立连接。之后我的应用程序再次完美连接。或者等待几分钟,直到 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;
    }

My monotouch app is doing periodic background synchronization with a web-service. It runs perfectly and detects Airplane mode correctly. When I switch off WiFi, it automatically starts using the WWAN (GPRS, 3G) connection. So far I'm very satisfied, but... After switching off Airplan Mode there is no way my app will reconnect when there is no WiFi available.

It detects correctly using a NetworkReachability object that WWAN is available and that a connection is required. But the first try times out (after 90 seconds I abort the running request using a timer). When I try again I get a WebException "Error: ConnectionFailure (No route to host)" as soon as I call EndGetRequestStream. The only way to connect again is to start another app, like Mail, which makes a connection. After that my app connects flawlessly again. Or to wait a few minutes until the iPhone goes to sleep. After a wake up, the connection is set up ok again.

What am I doing wrong?

The code below is started using 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;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

娇纵 2024-10-17 13:23:58

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

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

该 api 只接受 HTTP/HTTPs uri,并将快速连接到指定的 API,以重新唤醒所有连接的 WWAN,此时 WWAN 将保持活动状态,直到再次飞行或超时。

MonoTouch high level objects (ftp, smtp, http) that process network transactions utilize BSD sockets. Apple has a mechanism where even if the 3G/EDGE connection is "alive" its actually put to sleep. The only way to wake this up is to use CFStream or NSStream resources, there is no publically exposed API to wake up a GPRS connection for a BSD socket. Thankfully you can work around this issue. MonoTouch has provided an API:

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

This api only accepts HTTP/HTTPs uri's and will make a quick connection to the specified API to re-awaken the WWAN for all connections, at which point the WWAN will stay alive until airplaned or timed out again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文