随着互联网技术以及各种云服务的发展,设备之间的连通变得日益重要。
在这个移动计算的时代,无论在工作还是娱乐,多人合作的可能性变得空前巨大;也是这个注重隐私却充斥着监控的时代,对安全通信的需求也变得空前巨大;同时也是这个充满连接的时代,即便是随处可见的生活小物件也拥有者大方异彩的前景。
所以,iOS7 引入的 Multipeer Connectivity API 可能会成为最优秀的连接设备方式。它让开发者可以重新考虑移动 APP 的构建,重新定义了连接的可能性。
Multipeer Connectivity 允许附近的设备通过基本的 WIFI 网络,peer-to-peer WIFI 和蓝牙个人局域网来通信。连接了的设备之间可以加密地传输信息或者流数据,或者是文件,而没有必要通过中转互联网设备。
Advertising(广播) & Discovering(发现)
通信的第一步就是让设备彼此发现对方,而这通过Advertising以及Discovering这两个服务来实现。
Advertising 使得某个 service(服务) 能被其他设备知道,而 discovery 则是相对的过程,它使得 client 能发现那些被广播出来的服务。在大多数实际的开发环境下,client 会同时实现对同个服务的 discover 以及 advertise。可能这会引起一些疑惑,特别是对那些习惯于 c-s 架构的开发者。
每一个服务都用一个字符串来辨别,称为 ServiceType。字符串可包含 ASCII 字符,数字以及破折号,最大长度为 15 个字符。按照惯例,service name 应该用 app name 来作为开头,然后是破折号再加上对这个服务的描述:1
let CONNECTIVITY_SERVICE_TYPE = "kiwi-drone"
每个 peer 利用 MCPeerID
对象作为唯一标识。MCPeerID
对象使用一个 name 来初始化,可使用用户自定义的名称,又或者是使用当前设备的名称:1
let localPeerID = MCPeerID(displayName: UIDevice.currentDevice().name)
Peers 也可以通过手动使用
NSNetService
或者 Bonjour C APIs 来发现,但是这需要更底层的知识以及经历。关于手动管理 peer 的信息可以在MCSession
相关的文档里面找到。
Advertising
Service 是通过 MCNearbyServiceAdvertiser
来广播出去的,它需要通过 peer,service type 还有一些其他可选的附加信息(discovery information)来进行初始化。1
2
3let advertiser = MCNearbyServiceAdvertiser(peer: localPeerID, discoveryInfo: nil, serviceType: CONNECTIVITY_SERVICE_TYPE)
advertiser.delegate = self
advertiser.startAdvertisingPeer()
Discovery information is sent as Bonjour TXT records encoded according to RFC 6763.
可在 advertiser 的 delegate 中执行一些事件处理。举个例子: client 收到来自某个 peer 的连接请求,并且同意建立连接:1
2
3
4
5func advertiser(advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: NSData?, invitationHandler: (Bool, MCSession) -> Void) {
let session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .None)
session.delegate = self
invitationHandler(true, session)
}
Creating a Session
在上面的例子中,session 由 advertiser 创建,然后在同意连接请求的时候传送到请求连接的 peer 那边。在初始化 session 时:
- securityIdentity 是一个可选的参数,允许 peer 之间通过 X.509 证书安全地识别对方。如果需要传入这个参数,那么第一个元素应该是
SecIdentityRef
, 标识当前 client。后面可以添加一个或者多个SecCertificateRef
对象,用来验证 peer 的身份。 - encryptionPreference 参数指定了是否需要加密 peer 之间的通信。
虽然开启加密选项能够提升安全性,但是也会显著降低传输速率。所以,除非应用之间传输的是用户的敏感数据,否则还是推荐使用
MCEncryptionNone
作为选项。
MCSessionDelegate
协议会在后面发送以及接受消息的章节中再详细展开。
Discovering
Client 可是使用 MCNearbyServiceBrowser
来发现广播的服务,它通过当前的本地 peerID 以及 service type 来初始化,与前面提到的 MCNearbyServiceAdvertiser
类似:1
2
3let browser = MCNearbyServiceBrowser(peer: localPeerID, serviceType: CONNECTIVITY_SERVICE_TYPE)
browser.delegate = self
browser.startBrowsingForPeers()
周围可能存在多个 peer 在广播特定的服务,为了方便用户以及开发者,苹果提供了 MCBrowserViewController
可以让我们快速地连接正在广播的 peer:1
2
3
4
5
6
7
8
9
10let browser = MCNearbyServiceBrowser(peer: localPeerID, serviceType: CONNECTIVITY_SERVICE_TYPE)
browser.delegate = self
browser.startBrowsingForPeers()
let session = MCSession(peer: localPeerID, securityIdentity: nil, encryptionPreference: .None)
let browserViewCon = MCBrowserViewController(browser: browser, session: session)
browserViewCon.delegate = self
self.presentViewController(browserViewCon, animated: true) {
browser.startBrowsingForPeers()
}
当 browser 完成对 peer 的连接之后,它会调用 delegate 的 browserViewControllerDidFinish:
方法,然后可以做一些必要的界面或者其他更新操作。
Sending & Receiving Information
当 peer 之间建立了连接之后,信息就可在彼此之间传递了。传输的数据种类有三种:
- Messages: 是具有明确边界的信息,例如短文本或者较小的序列化对象
- Streams: 是一个打开的信息通道,用于频繁传输的数据,例如音视频或者实时的传感器事件
- Resources: 为典型文件,例如图片,电影,文档等等
Messages
Message 的传输样例如下:1
2
3
4
5
6
7let message = "Hello, world!"
let data = message.dataUsingEncoding(NSUTF8StringEncoding)!
do {
try session.sendData(data, toPeers: [connectedPeerID], withMode: .Unreliable)
} catch {
NSLog("Send data error:\(error)")
}
Message 的接收是通过 MCSessionDelegate
的方法来实现的。下面这个例子可以看到作为接收方如何获取上面例子中发送出去的 message:1
2
3
4func session(session: MCSession, didReceiveData data: NSData, fromPeer peerID: MCPeerID) {
let message = String(data: data, encoding: NSUTF8StringEncoding)
NSLog("Receive message")
}
上面例子发送的数据使用的是 String,而实际上当然也可可以使用 NSKeyedArchiver
来传输序列化对象数据。
但是,为了防护”对象替换攻击(Object Subsitution Attack)”, 我们需要将
requiresSecureCoding
设置为 true。而因此我们传输的对象需要实现NSSecureCoding
协议。这里可以获取更多信息NSSecureCoding。
Streams
Stream 的创建如下,使用 startStreamWithName:toPeer
1
2
3
4let outputStream = session.startStreamWithName("stream", toPeer: connectedPeer)
outputStream.delegate = self
outputStream.scheduleInRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream.open()
在接收方,同样是通过 MCSessionDelegate
的方法1
2
3
4
5func session(session: MCSession, didReceiveStream stream: NSInputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
stream.delegate = self
stream.scheduleInRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
stream.open()
}
发送数据以及接受数据都应该放在 Stream 的 delegate 中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch eventCode {
case NSStreamEvent.OpenCompleted:
break
case NSStreamEvent.HasBytesAvailable:
break
case NSStreamEvent.HasSpaceAvailable:
break
case NSStreamEvent.ErrorOccurred:
break
case NSStreamEvent.EndEncountered:
break
case NSStreamEvent.None:
break
}
}
Resources
Resource 的传输方法如下:1
2
3
4
5
6let fileURL = NSURL.fileURLWithPath("path/to/resource")
let progress = session.sendResourceAtURL(fileURL, withName: fileURL.lastPathComponent!, toPeer: connectedPeer) { (error) in
if let error = error {
NSLog("error")
}
}
返回的 NSProgress
对象可以使用 KVO 的方式来监控文件的传输进度,同时也提供了取消的方法:cancel
Resource 的接受通过 MCSessionDelegate
中的两个方法来实现:1
2
3
4
5
6
7
8
9
10
11
12func session(session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, withProgress progress: NSProgress) {
//TODO:
}
func session(session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, atURL localURL: NSURL, withError error: NSError?) {
let destinationURL = NSURL.fileURLWithPath("/path/to/destination")
do {
try NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: destinationURL)
} catch {
NSLog("Move Resource error: \(error)")
}
}
同样,接收方 peer 可以通过监听 delegate 中获取到的 progress 来知道文件的传输进程。当接受完成之后,接收方需要将文件从临时目录中转移出来。
Multipeer Connectivity 是一套具有开拓性的 API,而它的价值正在被挖掘。当下能够使用 AirDrop 的设备也越来越多,沟通连接的便捷性变得无比之高。未来的具有无限可能性。