GKSession peer disconnect causes other peers to ap

2019-02-19 04:22发布

问题:

My app uses GKSession with GKSessionModePeer. It has to handle peers arbitrarily connecting and disconnecting because this is a long running app and users should be able to go to background and come back later. This works fine most of the time. But sometimes, when a peer disconnects, other devices get notified with didChangeState:GKPeerStateDisconnected not only for the device that really disconnected but also for other devices that are actually still connected.

I can reproduce this behavior with the code below and 4 devices (all on iOS 5). When everything goes as expected, when device A quits the app, all other devices get notified and the log output on those devices is:

Service: didChangeState: peer A disconnected (12345)

But after a while, when a device disconnects (say A again), other devices will get additional callbacks for devices that didn't disconnect. For example, device C would get:

Service: didChangeState: peer A disconnected (...) // expected

Service: didChangeState: peer B disconnected (...) // never disconnected

Around the same time I am sometimes seeing these kind of messages in the logs of the disconnecting device, not clear if they are actually related:

dnssd_clientstub DNSServiceRefDeallocate called with NULL DNSServiceRef

and/or

dnssd_clientstub DNSServiceProcessResult called with DNSServiceRef with no ProcessReply function

Once this happens, GKSession seems to be in a bad state and no longer correctly handles connects and disconnects. To get back into a good state I have to hard kill the app on all devices, wait a little, and start over.

I have tried different ways of handling GKSession when going to background (only setting available=NO and not disconnecting, not doing anything at all), none of which worked any better.

Has anybody else run into this behavior (and solved it)?

Simple repro case in an AppDelegate (using arc):

- (void)startGKSession 
{
    self.gkSession = [[GKSession alloc] initWithSessionID:nil displayName:nil sessionMode:GKSessionModePeer];
    gkSession.disconnectTimeout = 10;
    gkSession.delegate = self;
        gkSession.available = YES;
}

- (void)shutdownGKSession 
{
    gkSession.available = NO;
    [gkSession disconnectFromAllPeers];
    gkSession.delegate = nil;    
    gkSession = nil;
    [self.connectedDevices removeAllObjects];
}

- (void)connectToPeer:(NSString *)peerId 
{
    [gkSession connectToPeer:peerId withTimeout:10];
}

- (void)session:(GKSession *)session peer:(NSString *)peerId didChangeState:(GKPeerConnectionState)state 
{

        switch (state) {
                case GKPeerStateAvailable:
            NSLog(@"Service: didChangeState: peer %@ available, connecting (%@)", [session displayNameForPeer:peerId], peerId);
            [self performSelector:@selector(connectToPeer:) withObject:peerId afterDelay:.5];            
                        break;

                case GKPeerStateUnavailable:
                        NSLog(@"Service: didChangeState: peer %@ unavailable (%@)", [session displayNameForPeer:peerId], peerId);
                        break;

                case GKPeerStateConnected:
            NSLog(@"Service: didChangeState: peer %@ connected (%@)", [session displayNameForPeer:peerId], peerId);
                        break;

                case GKPeerStateDisconnected:
                        NSLog(@"Service: didChangeState: peer %@ disconnected (%@)", [session displayNameForPeer:peerId], peerId);
                        break;

                case GKPeerStateConnecting:
                        NSLog(@"Service: didChangeState: peer %@ connecting (%@)", [session displayNameForPeer:peerId], peerId);
                        break;
        }
}

- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID 
{
    [session acceptConnectionFromPeer:peerID error:nil];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.connectedDevices = [[NSMutableArray alloc] init];
    [self startGKSession];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{    
    [self shutdownGKSession];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [self startGKSession];
}

@end

回答1:

I heard from apple support that this disconnect behavior is happening because devices connect "through" each other. For example, device A connects to device C through device B. If device B drops, device A will see device C disconnect and reconnect right away. I have not heard if/when this will be fixed.



回答2:

This is probably too late, but I think that if you change the session mode from GKSessionModePeer to GKSessionModeServer on the server it'll fix the issue.

Basically with peer they all connect to each other, the server one technically works the same way, but you get proper notifications for disconnects.