iosxamarin.iosios-simulatorcfstreamcfsocket

Issues with MonoTouch and CFStream API


I am currently developing an application that needs to communicate with a MQTT server via a socket connection. As the System.Net.Sockets API tends to misbehave when switching from a WiFi network to a 3G network (and this happens quite a lot actually) I've decided to give the CFStream API a try. In doing so I've encountered several issues, as follows.

Creating a pair of streams with CreatePairWithSocketToHost crashes the application as soon as I call Open() on either one of the streams.

CFStream.CreatePairWithSocketToHost(GetEndPoint(), out mReadStream, out mWriteStream);
mReadStream.EnableEvents(CFRunLoop.Current, CFRunLoop.CFDefaultRunLoopMode);
mWriteStream.EnableEvents(CFRunLoop.Current, CFRunLoop.CFDefaultRunLoopMode);
mReadStream.Open();
mWriteStream.Open();

The crash occurs regardless of whether I call EnableEvents() or not. The exception is:

[ERROR] FATAL UNHANDLED EXCEPTION: MonoTouch.CoreFoundation.CFException: The operation couldn’t be completed. Cannot allocate memory
  at MonoTouch.CoreFoundation.CFStream.CheckError () [0x0000f] in /Developer/MonoTouch/Source/monotouch/src/shared/CoreFoundation/CFStream.cs:236 
  at MonoTouch.CoreFoundation.CFStream.Open () [0x00040] in /Developer/MonoTouch/Source/monotouch/src/shared/CoreFoundation/CFStream.cs:248 
  at TestCfNework.RootViewController.TestCreatePairToHost () [0x00041] in /Users/adrian/Projects/TestCfNework/TestCfNework/RootViewController.cs:79 
  at TestCfNework.RootViewController.ViewDidLoad () [0x00000] in /Users/adrian/Projects/TestCfNework/TestCfNework/RootViewController.cs:24 
  at MonoTouch.UIKit.UIWindow.MakeKeyAndVisible () [0x00008] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIWindow.g.cs:124 
  at TestCfNework.AppDelegate.FinishedLaunching (MonoTouch.UIKit.UIApplication app, MonoTouch.Foundation.NSDictionary options) [0x0002e] in /Users/adrian/Projects/TestCfNework/TestCfNework/AppDelegate.cs:32 
  at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38 
  at TestCfNework.Application.Main (System.String[] args) [0x00000] in /Users/adrian/Projects/TestCfNework/TestCfNework/Main.cs:17 

Creating a pair of streams with CreatePairWithSocket by first creating and connecting a CFSocket allows for Open() to proceed without crashing, but CanAcceptBytesEvent is never fired, CanAcceptBytes() is always false and any write attempt fails with a timeout.

mSocket = new CFSocket(AddressFamily.InterNetwork, 
    SocketType.Stream, 
    ProtocolType.Tcp, 
    CFRunLoop.Current);
mSocket.ConnectEvent += delegate {
    Console.WriteLine("Socket connected");

    CFStream.CreatePairWithSocket(mSocket, out mReadStream, out mWriteStream);
    mReadStream.EnableEvents(CFRunLoop.Current, CFRunLoop.CFDefaultRunLoopMode);
    mWriteStream.EnableEvents(CFRunLoop.Current, CFRunLoop.CFDefaultRunLoopMode);

    mReadStream.Open();
    mWriteStream.Open();

    mWriteStream.CanAcceptBytesEvent += delegate {
        Console.WriteLine("Write stream can now accept data");
    };
    mWriteStream.ErrorEvent += delegate {
        Console.WriteLine(mWriteStream.GetError());
    };
};
mSocket.Connect(GetEndPoint(), 0);

Creating a pair of streams using CreatePairWithPeerSocketSignature is the only one that actually produces a pair of streams that I can work with: opening does not crash and I am allowed to write to and read from respectively.

The API behaves this way both on the simulator and on the actual device. So, is this something I am doing wrong? Is it a MonoTouch issue? Is it a bug in the CFStream API itself?

MonoTouch version: 6.0.1. XCode version: 4.5.


Solution

  • I wrote most the CFNetwork code in MonoMac / MonoTouch, so I should hopefully be able to help you with this :-)

    Your code looks ok to me. It works fine with MonoMac (stand-alone Cocoa application on the Mac), but I see the same problem with MonoTouch. Opening the read stream sometimes works, sometimes not, opening the write stream always fails.

    CFStream.CreatePairWithSocketToHost() calls CFStreamCreatePairWithSocketToCFHost().

    After adding a new overloaded version:

        public static void CreatePairWithSocketToHost (string host, int port,
                                                       out CFReadStream readStream,
                                                       out CFWriteStream writeStream)
    

    which calls CFStreamCreatePairWithSocketToHost(), this is now working fine.

    I just had a look at this and found the problem, will have a fix for it shortly.

    About your second problem, the CFSocket API is hitting the same code path internally, so it's also affected by this bug.