iOS7新技术:如何使用Multipeer Connectivity

Multipeer Connectivity是一个使附近设备通过Wi-Fi网络、P2P Wi-Fi以及蓝牙个人局域网进行通信的框架。互相连接的节点可以不适用网络而安全地传递信息、流或是其他文件资源。

Advertising & Discovering
        通信的第一步是让大家互相发现彼此,我们通过广播(Advertising)和发现(Discovering)服务来实现。
        广播(Advertising)作为服务器,来搜索附近的节点,而节点同时也去搜索附近的广播。在很多情况下,客户端同时广播并且发现同一个服务,这可能导致一些混乱的场面,尤其是在Client-Server模式中。所以,每一个服务都应该有一个标示符来表明自己,它必须是由ASCII字母、数字和“-”组成的短文本串,最多15个字符。我建议,一个服务的名字应该由应用程序的名字开始,后边跟“-”和一个自定义的描述符号。(作者认为这和 com.apple.*标示符很像),就像下边:
  1. static NSString * const XXServiceType = @“xx-service”;
        每个节点都有一个唯一名称的MCPeerID对象,使用字符串名称进行初始化,它可能是用户指定的昵称,亦或是单纯的设备名称。
  1. MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
        节点内部使用NSNetService或者Bonjour C API进行手动广播和发现,但这是一个值得深入研究的问题,关于手动节点管理可具体参见MCSession文档,在此将不进行深入。
Advertising
        服务的广播通过MCNearbyServiceAdvertiser来操作,初始化时带着本地节点、服务类型以及任何可与发现该服务的节点进行通信的可选信息。
       可选信息使用Bonjour TXT records encoded(according to RFC 6763)编码进行发送。
  1. MCNearbyServiceAdvertiser *advertiser =
  2.     [[MCNearbyServiceAdvertiser alloc] initWithPeer:localPeerID
  3.                                       discoveryInfo:nil
  4.                                         serviceType:XXServiceType];
  5. advertiser.delegate = self;
  6. [advertiser startAdvertisingPeer];
        相关事件由Advertiser的代理来处理,需遵从MCNearbyServiceAdvertiserDelegate协议。
        在下例中,考虑到用户可以选择是否接受或拒绝传入的连接请求,并我们有权拒绝或屏蔽任何来自该节点的后续请求。
  1. #pragma mark – MCNearbyServiceAdvertiserDelegate
  2. – (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
  3. didReceiveInvitationFromPeer:(MCPeerID *)peerID
  4.        withContext:(NSData *)context
  5.  invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
  6. {
  7.     if ([self.mutableBlockedPeers containsObject:peerID]) {
  8.         invitationHandler(NO, nil);
  9.         return;
  10.     }
  11.     [[UIActionSheet actionSheetWithTitle:[NSString stringWithFormat:NSLocalizedString(@“Received Invitation from %@”, @“Received Invitation from {Peer}”), peerID.displayName]
  12.                        cancelButtonTitle:NSLocalizedString(@“Reject”, nil)
  13.                   destructiveButtonTitle:NSLocalizedString(@“Block”, nil)
  14.                        otherButtonTitles:@[NSLocalizedString(@“Accept”, nil)]
  15.                                    block:^(UIActionSheet *actionSheet, NSInteger buttonIndex)
  16.     {
  17.         BOOL acceptedInvitation = (buttonIndex == [actionSheet firstOtherButtonIndex]);
  18.         if (buttonIndex == [actionSheet destructiveButtonIndex]) {
  19.             [self.mutableBlockedPeers addObject:peerID];
  20.         }
  21.         MCSession *session = [[MCSession alloc] initWithPeer:localPeerID
  22.                                             securityIdentity:nil
  23.                                         encryptionPreference:MCEncryptionNone];
  24.         session.delegate = self;
  25.         invitationHandler(acceptedInvitation, (acceptedInvitation ? session : nil));
  26.     }] showInView:self.view];
  27. }
        为了简单起见,本例中使用了一个带有Block的ActionSheet来作为操作框,它可以直接给invitationHandler传递信息,用来避免创建和管理delegate造成的过于凌乱的业务逻辑。这种方法可以用category来实现。
Creating a Session
        在下面的例子中,我们创建了session,并在接受连接时传递到节点。一个MCSession对象的初始化需要本地节点标识符、securityIdentity以及encryptionPreference等参数。
  1. MCSession *session = [[MCSession alloc] initWithPeer:localPeerID
  2.                                     securityIdentity:nil
  3.                                 encryptionPreference:MCEncryptionNone];
  4. session.delegate = self;
        securityIdentity是一个可选参数。通过X.509证书,它允许节点安全识别并连接其他节点。当设置了该参数时,第一个对象应该是识别客户端的SecIdentityRef,接着是一个或更多个用以核实本地节点身份的SecCertificateRef Objects。
        encryptionPreference参数指定是否加密节点之间的通信。MCEncryptionPreference枚举提供的三种值是:
MCEncryptionOptional:会话更喜欢使用加密,但会接受未加密的连接。
MCEncryptionRequired:会话需要加密。
MCEncryptionNone:会话不应该加密。
        启用加密会显著降低传输速率,所以除非你的应用程序很特别,需要对用户的敏感信息进行处理,否则建议使用MCEncryptionNone。
        MCSessionDelegate协议将会在发送和接受信息的部分被覆盖.
Discovering
        客户端使用MCNearbyServiceBrowser来发现广播,它需要Local Peer标识符,以及非常类似MCNearbyServiceAdvertiser的服务类型来初始化:
  1. MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType];
  2. browser.delegate = self;
        可能会有很多节点广播一个特定的服务,所以为了方便用户(或开发者),MCBrowserViewController将提供一个内置的、标准的方式来表示连接:
  1. MCBrowserViewController *browserViewController =
  2.     [[MCBrowserViewController alloc] initWithBrowser:browser
  3.                                              session:session];
  4. browserViewController.delegate = self;
  5. [self presentViewController:browserViewController
  6.                    animated:YES
  7.                  completion:
  8. ^{
  9.     [browser startBrowsingForPeers];
  10. }];
        当browser完成节点连接后,它将使用它的delegate调用browserViewControllerDidFinish:,用以通知展示视图控制器–它应该更新UI以适应新连接的客户端。
Sending & Receiving Information
        一旦节点彼此相连,它们将能互传信息。Multipeer Connectivity框架区分三种不同形式的数据传输:
Messages是定义明确的信息,比如端文本或者小序列化对象。
Streams 流是可连续传输数据(如音频,视频或实时传感器事件)的信息公开渠道。
Resources是图片、电影以及文档的文件。
Messages
Messages使用-sendData:toPeers:withMode:error::方法发送。
  1. NSString *message = @“Hello, World!”;
  2. NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
  3. NSError *error = nil;
  4. if (![self.session sendData:data
  5.                     toPeers:peers
  6.                    withMode:MCSessionSendDataReliable
  7.                       error:&error]) {
  8.     NSLog(@“[Error] %@”, error);
  9. }
        通过MCSessionDelegate方法 -sessionDidReceiveData:fromPeer:收取信息。以下是如何解码先前示例代码中发送的消息:
  1. #pragma mark – MCSessionDelegate
  2. – (void)session:(MCSession *)session
  3.  didReceiveData:(NSData *)data
  4.        fromPeer:(MCPeerID *)peerID
  5. {
  6.     NSString *message =
  7.         [[NSString alloc] initWithData:data
  8.                               encoding:NSUTF8StringEncoding];
  9.     NSLog(@“%@”, message);
  10. }
        另一种方法是发送NSKeyedArchiver编码的对象:
  1. id <NSSecureCoding> object = // …;
  2. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
  3. NSError *error = nil;
  4. if (![self.session sendData:data
  5.                     toPeers:peers
  6.                    withMode:MCSessionSendDataReliable
  7.                       error:&error]) {
  8.     NSLog(@“[Error] %@”, error);
  9. }
  10. #pragma mark – MCSessionDelegate
  11. – (void)session:(MCSession *)session
  12.  didReceiveData:(NSData *)data
  13.        fromPeer:(MCPeerID *)peerID
  14. {
  15.     NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
  16.     unarchiver.requiresSecureCoding = YES;
  17.     id object = [unarchiver decodeObject];
  18.     [unarchiver finishDecoding];
  19.     NSLog(@“%@”, object);
  20. }
        为了防范对象替换攻击,设置requiresSecureCoding为YES很重要,这样的话如果根对象类没有遵从<NSSecureCoding>,就会抛出一个异常。欲了解更多信息,请参阅[NSHipster article on NSSecureCoding]。
Streams
Streams 使用 -startStreamWithName:toPeer:创建:
  1. NSOutputStream *outputStream =
  2.     [session startStreamWithName:name
  3.                           toPeer:peer];
  4. stream.delegate = self;
  5. [stream scheduleInRunLoop:[NSRunLoop mainRunLoop]
  6.                 forMode:NSDefaultRunLoopMode];
  7. [stream open];
  8. // …
        Streams通过MCSessionDelegate的方法session:didReceiveStream:withName:fromPeer:来接收:
  1. #pragma mark – MCSessionDelegate
  2. – (void)session:(MCSession *)session
  3. didReceiveStream:(NSInputStream *)stream
  4.        withName:(NSString *)streamName
  5.        fromPeer:(MCPeerID *)peerID
  6. {
  7.     stream.delegate = self;
  8.     [stream scheduleInRunLoop:[NSRunLoop mainRunLoop]
  9.                       forMode:NSDefaultRunLoopMode];
  10.     [stream open];
  11. }
输入和输出的streams必须安排好并打开,然后才能使用它们。这样,streams就可以进行读和写的操作了。
Resources
        Resources 发送使用 -sendResourceAtURL:withName:toPeer:withCompletionHandler::
  1. NSURL *fileURL = [NSURL fileURLWithPath:@“path/to/resource”];
  2. NSProgress *progress =
  3.     [self.session sendResourceAtURL:fileURL
  4.                            withName:[fileURL lastPathComponent]
  5.                              toPeer:peer
  6.                   withCompletionHandler:^(NSError *error)
  7. {
  8.     NSLog(@“[Error] %@”, error);
  9. }];
         返回的NSProgress对象可以是通过KVO(Key-Value Observed)来监视文件传输的进度,并且它提供取消传输的方法:-cancel。
         接收资源实现MCSessionDelegate两种方法:-session:didStartReceivingResourceWithName:fromPeer:withProgress: 和 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:
  1. #pragma mark – MCSessionDelegate
  2. – (void)session:(MCSession *)session
  3. didStartReceivingResourceWithName:(NSString *)resourceName
  4.        fromPeer:(MCPeerID *)peerID
  5.    withProgress:(NSProgress *)progress
  6. {
  7.     // …
  8. }
  9. – (void)session:(MCSession *)session
  10. didFinishReceivingResourceWithName:(NSString *)resourceName
  11.        fromPeer:(MCPeerID *)peerID
  12.           atURL:(NSURL *)localURL
  13.       withError:(NSError *)error
  14. {
  15.     NSURL *destinationURL = [NSURL fileURLWithPath:@“/path/to/destination”];
  16.     NSError *error = nil;
  17.     if (![[NSFileManager defaultManager] moveItemAtURL:localURL
  18.                                                  toURL:destinationURL
  19.                                                  error:&error]) {
  20.         NSLog(@“[Error] %@”, error);
  21.     }
  22. }
        再次说明,在传输期间NSProgress parameter in -session:didStartReceivingResourceWithName:fromPeer:withProgress:允许接收节点来监控文件传输进度。在-session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:中,delegate的责任是从临时localURL移动文件至永久位置。
Multipeer是突破性的API,其价值才刚刚开始被理解。在不远的将来,你应该会看到它成为让所有人期望的功能。
本人翻译自:http://nshipster.com/multipeer-connectivity/

发表评论

电子邮件地址不会被公开。 必填项已用*标注