[Bonjour/NSNetService] Data written to NSOutputStream never makes it to the other side.

I have an iOS and a Mac app that establish a peer-to-peer connection through Bonjour/NSNetService. (based on the WiTap sample code)


In some cases, both devices won’t receive data anymore after a few seconds while they are still able to write data out (i.e. without an error being reported).


The data written to the NSOutputStream never makes it to the NSInputStream of the other side.


The strange part is that sending and receiving works right after the connection is established. It seems to go bad after a few seconds.I know, because I have each side send a “Ping” message between each other (literally the string “Ping\r\n”) every 4 seconds.


The first Ping makes it across each time, but in about 50% of the cases, all subsequent Ping commands get lost without an error being reported.


If it works, I get output like this:


iPhone: 1 [OK] Sent Ping to Mac
   Mac: 2 [OK] Got Ping from iPhone
   Mac: 3 [OK] Sent Ping to iPhone
iPhone: 4 [OK] Got Ping from Mac
iPhone: 5 [OK] Sent Ping to Mac
   Mac: 6 [OK] Got Ping from iPhone
   Mac: 7 [OK] Sent Ping to iPhone
iPhone: 8 [OK] Got Ping from Mac
        ... and so on ...


If it does not work:


iPhone: 1 [OK] Sent Ping to Mac
   Mac: 2 [OK] Got Ping from iPhone
   Mac: 3 [OK] Sent Ping to iPhone
iPhone: 4 [OK] Got Ping from Mac
iPhone: 5 [OK] Sent Ping to Mac
   Mac: 6 [OK] Sent Ping to iPhone
iPhone: 7 [!!] OVERDUE Ping from Mac
   Mac: 8 [!!] OVERDUE Ping from iPhone
iPhone: 9 [OK] Connection closed by user  ← I close the connection through the UI
   Mac: 0 [OK] Sent Ping to iPhone        ← Mac still sending happily ever after
   Mac: 1 [OK] Sent Ping to iPhone
   Mac: 2 [OK] Sent Ping to iPhone



Both devices are on the local WiFi network, Bluetooth is OFF on both devices.The iPhone publishes the NSNetService (includesPeerToPeer = true), the Mac discovers the service and connects to it.


Did anybody run into a similar issue? Is there any way I can track this down? I get a handle of the underlying socket every 4 seconds to check if it is valid, and it reports “true” each time.



Service code:


let server = NSNetService(domain: "local.", type: serviceType, name: serviceName, port: 0)
server.includesPeerToPeer = true
server.delegate           = self
server.publishWithOptions(.ListenForConnections)


Reading:


case NSStreamEvent.HasBytesAvailable where aStream == inputStream:
        
    var readBuffer = [UInt8](count: 3, repeatedValue: 0)

    let bytesRead = inputStream.read(&readBuffer, maxLength: readBuffer.count)

    if bytesRead > 0
    {
        inputBuffer.appendBytes(readBuffer, length: bytesRead)
    
        _processBufferAndNotifyDelegate(inputBuffer)
    }
    else
    {
        _closeStreamsAndNotifyDelegate()
    }



Writing:


case NSStreamEvent.HasSpaceAvailable where aStream == outputStream && outputBuffer.length > 0:
      
        outputStreamHasSpaceAvailable = true
      
        _startWritingOutputBuffer()


private func _startWritingOutputBuffer()
{
    if outputBuffer.length == 0 { return }

    let actuallyWritten = outputStream.write(UnsafePointer(outputBuffer.bytes), maxLength: outputBuffer.length)

    if actuallyWritten > 0
    {
        outputStreamHasSpaceAvailable = false
     
        // Remove from the buffer whatever we wrote. Remaining bytes, should there be any,
        // will be written on next HasSpaceAvailable event
        outputBuffer.replaceBytesInRange(NSMakeRange(0, actuallyWritten), withBytes: nil, length: 0)
    }
    else
    {
        // Error or end of file
        _closeStreamsAndNotifyDelegate()
    }
}



EDIT:


I ran some more tests and I'm starting to think that this could be an issue with having a Mac and an iOS device connected via Bluetooth. I added a basic Mac client to the WiTap sample code and can now reproduce this every time. At first, taps from iOS are received on the Mac, but if I stop tapping on the iPhone, the connection goes stale after a few seconds and the Mac won't receive any data. Both still think the connection is up and running, by the way. WiFi and Bluetooth are enabled on both devices.

Accepted Reply

Ok, thanks. I filed bug 24811362. The bug report includes a sample project based on the WiTap code to reproduce this issue.


I'm going to mark this thread as answered. Thanks a lot for your help!


For reference: solution/temporary workaround:


- use set includesPeerToPeer=false on your NSNetService to not use the peer-tp-peer connecton between Mac and iPhone.

Replies

Just FYI, OS X does not use Bluetooth for peer-to-peer Bonjour + TCP/IP networking, so that’s probably irrelevant. You can rule it out by simply turning Bluetooth off on both devices.

In your tests, are you using infrastructure Wi-Fi or peer-to-peer Wi-Fi? That is, are both devices on the same Wi-Fi?

If you’re using peer-to-peer Wi-Fi, you should test whether peer-to-peer is involved in this problem by disabling it on both ends (remove the code that sets

includesPeerToPeer
on the various Bonjour objects).

If not, post back and I’ll post my next round of suggestions.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks a lot for your suggestions! I've been trying to figure this out for a few days now...


Based on your suggestion I turned Bluetooth off on all devices and ran the following tests:


1. includesPeerToPeer = true; iPhone and Mac both part of the same Wi-Fi network


Result: Connection goes bad after a few seconds in about 50% of all cases as I described above.


2. includesPeerToPeer = false; iPhone and Mac both part of the same Wi-Fi network


Result: Seems to work. No problems so far.


3. includesPeerToPeer = true; iPhone and Mac have Wi-Fi enabled, but are NOT part of any Wi-Fi network (I turned the router off)


Result: Mac finds the service, opens the input/output streams, but never receives any stream events (i.e. no NSStreamEvent.OpenCompleted). Tried the same with the WiTap sample (I added a Mac client). Here, both establish a connection and I can send a few taps across, but as soon as I stop sending commands, the connection goes bad.



It looks like the peer-to-peer connectivity could indeed be the problem. I have 2 questions based on this:


1. Is there a way for me to see if the NSNetService I get on the Mac was established over a peer-to-peer or infrastructure Wi-Fi network?


2. Is peer-to-peer over Wi-Fi between iOS and OS X supported at all?

Thanks again for your help!

1. Is there a way for me to see if the NSNetService I get on the Mac was established over a peer-to-peer or infrastructure Wi-Fi network?

An NSNetService or an NS{Input,Output}Stream?

For an input or output stream you can get the source IP address of the stream (using

kCFStreamPropertySocketNativeHandle
to get the file descriptor and
getsockname
man page to get IP address from that) and map that to the network interface that the stream is running over (using
getifaddrs
man page).

2. Is peer-to-peer over Wi-Fi between iOS and OS X supported at all?

Yes. I recently posted about this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I tried getting the IP through the NSInput/Output streams and the NSNetServices.addresses property, but they all return 0.0.0.0. Getting the port works, by the way.


Anyway, with both devices on the same Wi-Fi network and using includesPeerToPeer=false, I get a stable connection.


About my 2. question:


I saw your other post (thanks again, by the way) about peer-to-peer being supported between iOS and Mac OS X starting with the MacBook Pro mid 2012 model.


Coincidentally I use a MacBook Pro mid 2012, but for me, the peer-to-peer connections do not work reliably (not even with the WiTap sample code, the connection dies after a few seconds each time).


I don't have a more recent Mac to test this with and am unsure to proceed. Could this be an issue with my particular MacBook or will all mid 2012 models have problems supporting peer-to-peer?

I don't have a more recent Mac to test this with and am unsure to proceed. Could this be an issue with my particular MacBook or will all mid 2012 models have problems supporting peer-to-peer?

Honestly, I don’t know. In general peer-to-peer Wi-Fi is supposed to either Just Work™, or not work at all. However, it is deeply entwined with the Wi-Fi hardware, and thus it’s easy to imagine it being flaky on specific models. I definitely recommend that you file a bug about what you’re seeing, but I also think it’d be worthwhile you tracking down some other Mac hardware to test on, just to make sure there’s not something specific to your environment that’s triggering this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Ok, thanks. I filed bug 24811362. The bug report includes a sample project based on the WiTap code to reproduce this issue.


I'm going to mark this thread as answered. Thanks a lot for your help!


For reference: solution/temporary workaround:


- use set includesPeerToPeer=false on your NSNetService to not use the peer-tp-peer connecton between Mac and iPhone.

Hello _Rico, I'm having the same problem but this time working with iPods and iPads. The problem is the same as yours, I'm streaming audio using Bonjour for get the streams. It is working if I start to send audio straight away once the stream is connected. But if I wait like 30 seconds not writing into the outputstream the input stream doesn't receive anything.



So , I have tried to switch includesPeerToPeer to false. Now it is working, but the problem is I need this to be working with only bluetooth too. Do you know if there is another way to solve this?


Thanks

Hi Pablo,


What hardware did you test this on (which MacBook generation)?


In an ealier comment above, Quinn said that "OS X does not useBluetooth for peer-to-peer Bonjour + TCP/IP networking".


The peer-to-peer issue I described in this thread was solely related to WiFi connections (I initially assumed that Bluetooth was involved, but as per Quinn's suggestion turned it off for all futher tests).


If you require a Bluetooth connection, you could try to use the Bluetooth API directly: https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/Bluetooth/BT_Bluetooth_On_MOSX/BT_Bluetooth_On_MOSX.html


I did not try this, because I did have the time and requiring WiFi was acceptable for me although getting it to work when both devices are not part of the same WiFi network would have been nice.


In fact, if peer-to-peer networking would work reliably, it would be almost as good as using Bluetooth. No configuration needed, both devices must have WiFi enabled and will find each other...


Cheers,

Rico

It is working if I start to send audio straight away once the stream is connected. But if I wait like 30 seconds not writing into the outputstream the input stream doesn't receive anything.

Last I checked the peer-to-peer interfaces between machines have an idle timer that shuts down the interface if it’s been idle for too long. It’s possible that this is what’s biting you here.

In general, you should only resolve a Bonjour service when you actively want to communicate with it (more on this below). That’s because the act of resolving the service does stuff that you only want do at the last minute. That’s true even in the infrastructure case (for example, resolving the service gets the remote peer’s IP address, so if the remote peer changes address between the resolve and the connect then things will fail) but it’s especially true in the peer-to-peer case, where resolving the service does heavyweight stuff like bringing up the peer-to-peer interface.

So, why is there such a big delay between resolving the service and sending data?

btw Resolve is one of the three fundamental Bonjour operations (the others being Publish and Browse). When you’re using NSStream, the resolve happens implicitly as part of the connection process, that is, when you open the stream. If you use lower-level APIs, you have to explicitly resolve the service before trying to communicate with it.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"