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.
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.