我使用 MCNearbyServiceBrowser 和 MCNearbyServiceAdvertiser 将两个对等体加入 MCSession。 我能够使用MCSession的sendData方法在它们之间发送数据。 一切似乎都在按预期工作,直到我随机(而不是由于我控制的任何事件)通过会话的 MCSessionDelegate didChangeState 处理程序收到 MCSessionStateNotConnected。 此外,MCSession 的互联对等阵列不再有我的对等方。
两个问题:为什么?和如何防止 MCSession 断开连接?
这是一个错误,我刚刚向Apple报告了。文档声称didReceiveCertificate
回调是可选的,但事实并非如此。将此方法添加到您的MCSessionDelegate
:
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
随机断开连接应停止。
更新 在使用Apple的支持票证后,他们确认过于频繁地调用sendData并且数据过多会导致断开连接。
我在遇到断点和后台时断开连接。由于断点不会在应用商店中发生,因此您需要在应用即将进入后台时启动后台任务来处理后台情况。然后在应用返回到前台时结束此任务。在iOS 7上,这为您提供了大约3分钟的背景时间,这总比没有好。
另一种策略是使用 [[UIApplication sharedApplication] backgroundTimeRemaining]
在后台时间到期之前安排本地通知 15 秒,这样您就可以在用户挂起并且必须关闭多对等体框架之前将用户带回应用程序。也许本地通知会警告他们他们的会话将在 10 秒后过期或其他什么......
如果后台任务过期并且应用程序仍在后台,则必须拆除与多对等连接相关的所有内容,否则将崩溃。
- (void) createExpireNotification
{
[self killExpireNotification];
if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
self.expireNotification = n;
self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
self.expireNotification.alertBody = TR(@"Text_MultiPeerIsAboutToExpire");
self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
self.expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
}
}
- (void) killExpireNotification
{
if (self.expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
self.expireNotification = nil;
}
}
- (void) applicationWillEnterBackground
{
self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self shutdownMultiPeerStuff];
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void) applicationWillEnterForeground
{
[self killExpireNotification];
if (self.taskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}
}
- (void) applicationWillTerminate
{
[self killExpireNotification];
[self stop]; // shutdown multi-peer
}
由于 Apple 错误,您还需要在 MCSession 委托中使用此处理程序:
- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
if (certificateHandler != nil) { certificateHandler(YES); }
}
造成这种情况的原因有很多,根据我的经验,到目前为止的两个答案都是正确的。 你会在其他类似的问题中找到另一个问题是:只有一个同伴可以接受另一个同伴的邀请。
因此,澄清一下,如果您设置了一个所有设备都是广告商和浏览器的应用程序,则任何设备都可以自由邀请找到的任何其他设备加入会话。 但是,在任何两个给定设备之间,只有一个设备可以实际接受邀请并连接到另一个设备。 如果两台设备都接受对方的邀请,它们将在一分钟或更短的时间内断开连接。
请注意,此限制不会阻止所需的行为,因为与我在构建多对等实现之前的直觉不同,当一台设备接受邀请并连接到另一台设备时,它们都会连接并接收连接委托方法,并且可以相互发送消息。
因此,如果您要连接既浏览又做广告的设备,请自由发送邀请,但只接受一对中的一个。
只接受两个邀请中的一个的问题可以通过多种方式解决。 首先,要了解,您可以将任意对象或字典(存档为数据)作为邀请中的context
参数传递。 因此,两个设备都可以访问有关另一个(当然还有它本身)的任何任意信息。 因此,您至少可以使用以下策略:
- 只需
compare:
对等 ID 的显示名称即可。 但不能保证这些不会相等。 - 存储多对等控制器的初始化日期,并使用该日期进行比较
- 给每个对等方一个UUID,并发送它进行比较(我的技术,其中每个设备(实际上是设备上应用程序的每个用户)都有一个它使用的持久UUID)。
- 等等 - 任何同时支持 NSCoding 和
compare:
的对象都可以正常工作。
我也遇到了类似的问题。 似乎如果我在一台iOS设备上运行我的应用程序,并连接到另一台,然后退出并重新启动(例如,当我从Xcode重新运行时),那么我的情况是我收到一条已连接消息,然后是未连接消息稍后。 这让我很失望。 但更仔细地看,我可以看到"未连接"消息实际上适用于与已连接的对等 ID 不同的对等 ID。
我认为这里的问题是,我见过的大多数样本只关心peerID的displayName,而忽略了您可以为同一设备/displayName获取多个peerID的事实。
我现在首先检查显示名称,然后通过比较指针来验证 peerID 是否相同。
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(@"remnant connection drop");
return; // note that I don't care if player is nil, since I don't want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...
我在接受连接请求后立即断开连接。 观察状态,我看到它从MCSessionStateConnected变为MCSessionStateNotConnected。
我正在创建我的会话:
[[MCSession alloc] initWithPeer:peerID]
不是处理安全证书的实例化方法:
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference
根据上面的安德鲁提示,我添加了委托方法
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
并且断开连接停止了。