Monotouch: WebRequest connection fails after switc

2019-02-05 13:38发布

问题:

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;
    }

回答1:

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.