1

I currently have two devices connected to each other over the multi-peer connectivity framework. When connected each device will instantly send a packet of nsdata to each other, in this first instance the data will contain a sound and image. This appears to work fine and both user receive the relevant data.

The users can now select each other from the view and send another bit of data. However i'm seeing that if the host (the client who sent the invitation) attempts to send some more data the following error is displayed in the console.

 Peers (
        PeerName
    ) not connected

But if the client (the invitee) sends data the it arrives as expected on the host. What is especially interesting is the fact that i have NSLogs in the:

-(void)peerDidChangeStateWithNotification:(NSNotification *)notification

Method which fire when a disconnect occurs, there are no disconnects occurring and the devices are connected to one another. My code is below, does anyone have any idea why this might occur?

#pragma mark Send Buzz Notification
#pragma mark -
-(void)sendBuzzNotification{

    // create the user (remove the image to lower size)
    LocalUserClass *withoutImage = [[LocalUserClass alloc]initWithName:[[[GlobalData sharedGlobalData]localUser]personName] personImage:nil personSound:[[[GlobalData sharedGlobalData]localUser]personSound] personPeerId:nil];

    // send the users sound and name to the other peer/s
    NSData *dataToSend = [NSKeyedArchiver archivedDataWithRootObject:withoutImage];
    NSError *error;

    // skip if no users are selcted
    if ([selectedUsers count] == 0) {
        return;
    }
    else{
        // create the array of peer Ids from selected users
        NSMutableArray *tempArray = [[NSMutableArray alloc]init];

        for (LocalUserClass *object in selectedUsers) {

            // add the peer id
            MCPeerID *theId = object.personPeerId;
            [tempArray addObject:theId];
        }
        NSArray *theUsers = [[NSArray alloc]init];
        theUsers = tempArray;

    // send the data
    [appDelegate.mcManager.session sendData:dataToSend
                                    toPeers:theUsers
                                   withMode:MCSessionSendDataReliable
                                      error:&error];

    if (error) {
        NSLog(@"%@", [error localizedDescription]);
    }

    // vibrate the device to show its been sent
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }
}
#pragma mark Peer Connected State Checker
#pragma mark -
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{

    MCPeerID *peerID = [[notification userInfo] objectForKey:@"peerID"];
    //NSLog(@"%@",peerID.displayName);
    MCSessionState state = [[[notification userInfo] objectForKey:@"state"] intValue];
    LocalUserClass *theUser = [[LocalUserClass alloc]initWithName:peerID.displayName personImage:nil personSound:nil personPeerId:peerID];
    //NSLog(@"the user name is: %@ the user sound is: %@ the user peer id is %@",theUser.personName,theUser.personSound,theUser.personPeerId);

    if (state != MCSessionStateConnecting) {

        if (state == MCSessionStateConnected) {

            // add the user
            [connectedUsers addObject:theUser];
            // send the users details to the newly connected peer (profile pic and sound)
            [self sendUserDetailsToPeer:peerID];

        }
        else if (state == MCSessionStateNotConnected){

            // do we have connections
            if ([connectedUsers count] > 0) {

                // get the user to remove
                NSInteger thePeerIndex = 0;
                NSInteger thePeerIndexSelectedUsers = 0;

                for (LocalUserClass *object in connectedUsers) {
                    if (object.personPeerId == theUser.personPeerId) {
                        thePeerIndex = [connectedUsers indexOfObject:object];
                        [connectedUsers removeObjectAtIndex:thePeerIndex];
                    }
                }
                for (LocalUserClass *user in selectedUsers) {
                    if (user.personPeerId == theUser.personPeerId) {
                    thePeerIndexSelectedUsers = [selectedUsers indexOfObject:user];
                    [selectedUsers removeObjectAtIndex:thePeerIndexSelectedUsers];
                    }
                }
            }
        }
    }

    // push to main queue for speedy response
    dispatch_async(dispatch_get_main_queue(), ^(void) {

        // create user playback devices
        [playbackManager createUserAudioPlayers:connectedUsers withSound:nil];

        [collView reloadData];
        [self populateUserBox];

        BOOL peersExist = ([[appDelegate.mcManager.session connectedPeers] count] == 0);
        //NSLog(@"PEER COUNT IS %lu",(unsigned long)[[appDelegate.mcManager.session connectedPeers] count]);
        [disconnectButton setEnabled:!peersExist];

        if ([disconnectButton isEnabled]) {
            [disconnectButton setBackgroundColor:[UIColor colorWithRed:(51/255.0) green:(202/255.0) blue:(168/255.0) alpha:1.0]];
        }
        else{
            [disconnectButton setBackgroundColor:[UIColor colorWithRed:(107/255.0) green:(107/255.0) blue:(107/255.0) alpha:1.0]];
        }

    });

}

#pragma mark Peer Received Data
#pragma mark -
-(void)didReceiveDataWithNotification:(NSNotification *)notification{

    NSData *receivedData = [[notification userInfo] objectForKey:@"data"];
    receivedUser = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];

    CGImageRef cgref = [receivedUser.personImage CGImage];
    CIImage *cim = [receivedUser.personImage CIImage];

     // are we getting a whole user or just the user sound and name (check for no image)
    if (cim == nil && cgref == NULL)
    {
        // recieve the data (TODO probably needs to be run in the background)
        [self performSelectorOnMainThread:@selector(recieveBuzzNotification) withObject:nil waitUntilDone:YES];
    }
    else{
        // recieve the data (TODO probably needs to be run in the background)
       [self performSelectorOnMainThread:@selector(receivedUserProfile) withObject:nil waitUntilDone:YES];
    }
}

UPDATE: The host appears to lose connection to the client after the inital data transfer:

[self sendUserDetailsToPeer:peerID];

However the disconnection state doesn't fire anything, and the client can still send data to the host. this is seen by logging out the connected sessions:

session.connectedPeers

EDIT: The additional code from the view controller:

#pragma mark Create Session
#pragma mark -
-(void)setupSession{

    // create session and peer with details from the user
    [appDelegate.mcManager setupPeerAndSessionWithDisplayName:[[[GlobalData sharedGlobalData]localUser]personName]];
    [appDelegate.mcManager advertiseSelf:visibleSwitch.isOn];
    appDelegate.mcManager.browser.delegate = self;

    // set global user peerId
    [[[GlobalData sharedGlobalData]localUser]setPersonPeerId:appDelegate.mcManager.peerIDGlobal];

    // setup notification to track the user info of connected peers
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(peerDidChangeStateWithNotification:)
                                                 name:@"MCDidChangeStateNotification"
                                               object:nil];
    // notifcation to track recieved data
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveDataWithNotification:)
                                                 name:@"MCDidReceiveDataNotification"
                                               object:nil];
}

and the MCManager:

-(id)init{
    self = [super init];

    if (self) {
        peerIDGlobal = nil;
        session = nil;
        browser = nil;
        advertiser = nil;
    }

    return self;
}

//----CUSTOM---------------------------------------------------------------------------------------------------------------//

//-------------------------------------------------------------------------------------------------------------------------//
//----PUBLIC---------------------------------------------------------------------------------------------------------------//
#pragma mark Setup Peer and Session
#pragma mark -
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{

    peerIDGlobal = [[MCPeerID alloc] initWithDisplayName:[[[GlobalData sharedGlobalData]localUser]personName]];
    session = [[MCSession alloc] initWithPeer:peerIDGlobal];
    session.delegate = self;
}

#pragma mark Setup Browser
#pragma mark -
-(void)setupMCBrowser{
    browser = [[MCBrowserViewController alloc] initWithServiceType:@"chat-files" session:session];
}

#pragma mark Setup Advertiser
#pragma mark -
-(void)advertiseSelf:(BOOL)shouldAdvertise{

    if (shouldAdvertise) {
        advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:@"chat-files"
                                                           discoveryInfo:nil
                                                                 session:session];
        [advertiser start];
    }
    else{
        [advertiser stop];
        advertiser = nil;
    }
}


//----DELEGATES------------------------------------------------------------------------------------------------------------//

//-------------------------------------------------------------------------------------------------------------------------//
#pragma mark Session delegate
#pragma mark -
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{

    // create the dict with connected user info
    NSDictionary *dict = @{@"peerID": peerID,
                           @"state" : [NSNumber numberWithInt:state]
                           };

    // post the notification for tracking
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidChangeStateNotification"
                                                        object:nil
                                                      userInfo:dict];
}


-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{

    NSDictionary *dict = @{@"data": data,
                           @"peerID": peerID
                           };

    [[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidReceiveDataNotification"
                                                        object:nil
                                                      userInfo:dict];
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
SmokersCough
  • 967
  • 7
  • 22
  • Do you see any activity if you use the delegate methods e.g. - (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID – Sten Oct 22 '14 at 05:44
  • Hi Sten, data is received on the host device when the client sends a message as i've got nslogging showing this occurs, however when the host attempts to send data back i'm not even seeing the delegate method being run. Any other ideas? – SmokersCough Oct 22 '14 at 11:48
  • On further investigation it appears that after initial connection the connection to the client is dropped on the host i check this by nslogging out: session.connectedpeers but the client can still send data to the host. – SmokersCough Oct 22 '14 at 12:21
  • Can you post the Multipeer Connectivity code you are using to connect the peers? – ChrisH Oct 23 '14 at 03:31
  • Hi Chris, i've added some more of the code, essentially the user goes into the browser window sends an invite as per the standard apple way of doing things then when the client accepts the: -(void)peerDidChangeStateWithNotification:(NSNotification *)notification is run – SmokersCough Oct 23 '14 at 11:47
  • How are you defining "host" and "client"? Do both devices get invitations? – ChrisH Oct 23 '14 at 20:31
  • Hi Chris, both devices can send and invite each other using the standard browser from the multipeer framework. So when i refer to host i mean the device that has sent the invite and the client is the device that accepts the invite. However both devices are capable of sending and accepting invites. Could this perhaps be an issue where the client is required to invite the host to make a two way connection. Although this seems a little overkill – SmokersCough Oct 26 '14 at 15:14
  • What do you get when you NSLog(@"%@",theUsers)? – JuJoDi Oct 29 '14 at 12:53
  • I'm getting an empty list when i NSLog the session.connectedpeers variable from the host device. This occurs after the initial data is sent (image and username) when the two devices connect – SmokersCough Oct 29 '14 at 13:18
  • 1
    @SmokersCough I suspect it's related to this issue: http://stackoverflow.com/questions/19469984/reconnecting-to-disconnected-peers. Can you try having only one device browsing, and one device advertising to see if that stops the connection from dropping? – ChrisH Oct 30 '14 at 01:01
  • Thanks Chris, this looks to be the issue, i've rewritten the code based on some boilerplate code from here: https://github.com/shrtlist/MCSessionP2P now the app automatically connects to users without browsers, it's a great solution and i highly recommend anyone with problems have a look at the code. – SmokersCough Oct 30 '14 at 22:25

3 Answers3

3

Thanks Chris, this looks to be the issue, i've rewritten the code based on some boilerplate code from here: https://github.com/shrtlist/MCSessionP2P

Now the app automatically connects to users without browsers, it's a great solution and i highly recommend anyone with problems have a look at the code.

yunas
  • 4,143
  • 1
  • 32
  • 38
SmokersCough
  • 967
  • 7
  • 22
0

Multipeer Connectivity has known problems when two devices browse/advertise and send/accept invitations at the same time.

The solution that Apple recommended during WWDC 14 is the one I posted here: Reconnecting to disconnected peers which uses a deterministic approach to deciding which peer in a pair accepts the other's invitation.

It seems that MCBrowserViewController and MCAdvertiserAssistant can only be reliably used in apps where one peer is browsing with the other(s) advertising.

Community
  • 1
  • 1
ChrisH
  • 4,468
  • 2
  • 33
  • 42
-1

I was having a similar problem, when devices were both advertising and browsing simultaneously. I used this method as suggested (I believe) in an Apple WWDC video to make sure that there wasn't a duplicate connection;

func browser(browser: MCNearbyServiceBrowser!, foundPeer peerID: MCPeerID!, withDiscoveryInfo info: [NSObject : AnyObject]!) {
    if (self.myPeerID.hash > peerID.hash){
        println("foundPeer:\(peerID.description) -> invitePeer")
        mpBrowser.invitePeer(peerID, toSession: self.mpSession, withContext: nil, timeout: peerInviteTimeout)   
    }else{
        println("foundPeer:  \(peerID.description) hash is higher->defer invite")     
    }
}
mflac
  • 353
  • 3
  • 16
  • Take a look at this answer: http://stackoverflow.com/questions/19469984/reconnecting-to-disconnected-peers using hash is potentially problematic. – ChrisH Nov 04 '14 at 18:48