iOS7新技术:如何使用Multipeer Connectivity

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

[[111258]]

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

Advertising & Discovering

通信的第一步是让大家互相知道彼此,我们通过广播(Advertising)和发现(discovering)服务来实现。

广播作为服务器搜索附近的节点,而节点同时也去搜索附近的广播。在许多情况下,客户端同时广播并发现同一个服务,这将导致一些混乱,尤其是在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.  
  3. - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser 
  4. didReceiveInvitationFromPeer:(MCPeerID *)peerID 
  5.        withContext:(NSData *)context 
  6.  invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler 
  7.     if ([self.mutableBlockedPeers containsObject:peerID]) { 
  8.         invitationHandler(NO, nil); 
  9.         return
  10.     } 
  11.  
  12.     [[UIActionSheet actionSheetWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Received Invitation from %@", @"Received Invitation from {Peer}"), peerID.displayName] 
  13.                        cancelButtonTitle:NSLocalizedString(@"Reject", nil) 
  14.                   destructiveButtonTitle:NSLocalizedString(@"Block", nil) 
  15.                        otherButtonTitles:@[NSLocalizedString(@"Accept", nil)] 
  16.                                    block:^(UIActionSheet *actionSheet, NSInteger buttonIndex) 
  17.     { 
  18.         BOOL acceptedInvitation = (buttonIndex == [actionSheet firstOtherButtonIndex]); 
  19.  
  20.         if (buttonIndex == [actionSheet destructiveButtonIndex]) { 
  21.             [self.mutableBlockedPeers addObject:peerID]; 
  22.         } 
  23.  
  24.         MCSession *session = [[MCSession alloc] initWithPeer:localPeerID 
  25.                                             securityIdentity:nil 
  26.                                         encryptionPreference:MCEncryptionNone]; 
  27.         session.delegate = self; 
  28.  
  29.         invitationHandler(acceptedInvitation, (acceptedInvitation ? session : nil)); 
  30.     }] showInView:self.view]; 
  31. }

为了简单起见,本例中使用了一个带有block的actionsheet来作为操作框,它可以直接给invitationHandler传递信 息,用以避免创建和管理delegate造成的过于凌乱的业务逻辑,以避免创建和管理自定义delegate object造成的过于凌乱的业务逻辑。这种方法可以用category来实现,或者改编任何一CocoaPods里有效的实现

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.   
  3. - (void)session:(MCSession *)session 
  4.  didReceiveData:(NSData *)data 
  5.        fromPeer:(MCPeerID *)peerID 
  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. #pragma mark - MCSessionDelegate 
  10.   
  11. - (void)session:(MCSession *)session 
  12.  didReceiveData:(NSData *)data 
  13.        fromPeer:(MCPeerID *)peerID 
  14.     NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 
  15.     unarchiver.requiresSecureCoding = YES; 
  16.     id object = [unarchiver decodeObject]; 
  17.     [unarchiver finishDecoding]; 
  18.     NSLog(@"%@", object); 
  19. }

为了防范对象替换攻击,设置requiresSecureCoding为YES是很重要的,这样如果根对象类没有遵从<NSSecureCoding>,就会抛出一个异常。欲了解更多信息,请参阅[NSHipster article on NSSecureCoding]。

Streams

Streams 使用 -startStreamWithName:toPeer:创建:

  1. NSOutputStream *outputStream = 
  2.     [session startStreamWithName:name 
  3.                           toPeer:peer]; 
  4.  
  5. stream.delegate = self; 
  6. [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] 
  7.                 forMode:NSDefaultRunLoopMode]; 
  8. [stream open]; 
  9.  
  10. // ...

Streams通过MCSessionDelegate的方法session:didReceiveStream:withName:fromPeer:来接收:

  1. #pragma mark - MCSessionDelegate 
  2.  
  3. - (void)session:(MCSession *)session 
  4. didReceiveStream:(NSInputStream *)stream 
  5.        withName:(NSString *)streamName 
  6.        fromPeer:(MCPeerID *)peerID 
  7.     stream.delegate = self; 
  8.     [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] 
  9.                       forMode:NSDefaultRunLoopMode]; 
  10.     [stream open]; 

输入和输出的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.     NSLog(@"[Error] %@", error); 
  8. }]; 

返回的NSProgress对象可以是通过KVO(Key-Value Observed)来监视文件传输的进度,并且它提供取消传输的方法:-cancel。

接收资源实现MCSessionDelegate两种方 法:-session:didStartReceivingResourceWithName:fromPeer:withProgress: 和 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:

  1. #pragma mark - MCSessionDelegate 
  2.  
  3. - (void)session:(MCSession *)session 
  4. didStartReceivingResourceWithName:(NSString *)resourceName 
  5.        fromPeer:(MCPeerID *)peerID 
  6.    withProgress:(NSProgress *)progress 
  7.     // ... 
  8.  
  9. - (void)session:(MCSession *)session 
  10. didFinishReceivingResourceWithName:(NSString *)resourceName 
  11.        fromPeer:(MCPeerID *)peerID 
  12.           atURL:(NSURL *)localURL 
  13.       withError:(NSError *)error 
  14.     NSURL *destinationURL = [NSURL fileURLWithPath:@"/path/to/destination"]; 
  15.     NSError *error = nil; 
  16.     if (![[NSFileManager defaultManager] moveItemAtURL:localURL 
  17.                                                  toURL:destinationURL 
  18.                                                  error:&error]) { 
  19.         NSLog(@"[Error] %@", error); 
  20.     } 

再次说明,在传输期间NSProgress parameter in -session:didStartReceivingResourceWithName:fromPeer:withProgress:允许接收节点来 监控文件传输进度。在 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError: 中,delegate的责任是从临时localURL移动文件至永久位置。

Multipeer是突破性的API,其价值才刚刚开始被理解。虽然完整的支持功能比如AirDrop目前仅限于最新的设备,你应该会看到它将成为让所有人盼望的功能。

本文由郭历成[博客]翻译自nshipster中的Multipeer Connectivity一节。

【移动开发视频课程推荐】

责任编辑:闫佳明 来源: cocoachina
相关推荐

2013-09-18 10:46:15

2013-09-26 14:42:28

2013-08-20 15:51:06

iOS7iOS7 Beta6苹果

2013-09-25 09:50:27

iOS7苹果

2013-09-26 16:02:13

2013-06-12 15:19:05

iOS7WWDC

2013-06-13 08:58:02

iOS7WWDCDesign By C

2013-07-24 17:45:15

iOS7图标UI

2013-09-16 10:14:35

iOS7App Store

2013-06-19 09:28:29

2013-12-17 10:02:30

越狱iOS7

2013-06-25 10:13:11

iOS7WWDC苹果

2013-10-29 14:31:33

移动技术半月刊

2013-07-01 13:41:09

iOS7WWDC姿势控制

2013-06-21 13:04:59

iOS7WWDCUI设计

2013-12-20 11:26:01

2013-11-25 17:32:04

微软Windows 9Aero

2022-02-07 09:07:38

黑客供应链攻击SolarWinds

2013-09-22 13:18:36

多径TCPiOS7

2013-07-30 09:12:39

移动技术半月刊
点赞
收藏

51CTO技术栈公众号