iOS开发–通过MultipeerConnectivity完成蓝牙通讯

iOS蓝牙通讯的三种方式:

  • GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期,但是目前已经被淘汰。(不做介绍)

  • MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。(详细介绍)

  • CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。


MultipeerConnectivity实现蓝牙通讯

前面已经说了GameKit相关的蓝牙操作类从iOS7已经全部过期,苹果官方推荐使用MultipeerConnectivity代替。但是应该了解,MultipeerConnectivity.framework并不仅仅支持蓝牙连接,准确的说它是一种支持Wi-Fi网络、P2P Wi-Fi已经蓝牙个人局域网的通信框架,它屏蔽了具体的连接技术,让开发人员有统一的接口编程方法。通过MultipeerConnectivity连接的节点之间可以安全的传递信息、流或者其他文件资源而不必通过网络服务。此外使用MultipeerConnectivity进行近场通信也不再局限于同一个应用之间传输,而是可以在不同的应用之间进行数据传输(当然如果有必要的话你仍然可以选择在一个应用程序之间传输)。

要了解MultipeerConnectivity的使用必须要清楚一个概念:广播(Advertisting)和发现(Disconvering),这很类似于一种Client-Server模式。假设有两台设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就试图建立连接,经过B同意二者建立连接就可以相互发送数据。在使用GameKit框架时,A和B既作为广播又作为发现,当然这种情况在MultipeerConnectivity中也很常见。

  • A.广播

无论是作为服务器端去广播还是作为客户端去发现广播服务,那么两个(或更多)不同的设备之间必须要有区分,通常情况下使用MCPeerID对象来区分一台设备,在这个设备中可以指定显示给对方查看的名称(display

name)。另外不管是哪一方,还必须建立一个会话MCSession用于发送和接受数据。通常情况下会在会话的-(void)session:(MCSession

)session peer:(MCPeerID )peerID didChangeState:(MCSessionState)state代理方法中跟踪会话状态(已连接、正在连接、未连接);在会话的-(void)session:(MCSession

)session didReceiveData:(NSData )data fromPeer:(MCPeerID *)peerID代理方法中接收数据;同时还会调用会话的-(void)sendData: toPeers:withMode: error:方法去发送数据。

广播作为一个服务器去发布自身服务,供周边设备发现连接。在MultipeerConnectivity中使用MCAdvertiserAssistant来表示一个广播,通常创建广播时指定一个会话MCSession对象将广播服务和会话关联起来。一旦调用广播的start方法周边的设备就可以发现该广播并可以连接到此服务。在MCSession的代理方法中可以随时更新连接状态,一旦建立了连接之后就可以通过MCSession的connectedPeers获得已经连接的设备。

  • B.发现

前面已经说过作为发现的客户端同样需要一个MCPeerID来标志一个客户端,同时会拥有一个MCSession来监听连接状态并发送、接受数据。除此之外,要发现广播服务,客户端就必须要随时查找服务来连接,在MultipeerConnectivity中提供了一个控制器MCBrowserViewController来展示可连接和已连接的设备(这类似于GameKit中的GKPeerPickerController),当然如果想要自己定制一个界面来展示设备连接的情况你可以选择自己开发一套UI界面。一旦通过MCBroserViewController选择一个节点去连接,那么作为广播的节点就会收到通知,询问用户是否允许连接。由于初始化MCBrowserViewController的过程已经指定了会话MCSession,所以连接过程中会随时更新会话状态,一旦建立了连接,就可以通过会话的connected属性获得已连接设备并且可以使用会话发送、接受数据。

下面用两个不同的应用程序来演示使用MultipeerConnectivity的使用过程,其中一个应用运行在模拟器中作为广播节点,另一个运行在iPhone真机上作为发现节点,并且实现两个节点的文字互传。

首先看一下作为广播节点的程序:

#import "ViewController.h"
#import <MultipeerConnectivity/MultipeerConnectivity.h> @interface ViewController ()<MCAdvertiserAssistantDelegate, MCSessionDelegate,
UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@property (strong,nonatomic) MCSession *session;
@property (strong,nonatomic) MCAdvertiserAssistant *advertiserAssistant;
@end @implementation ViewController
#pragma mark - 控制器视图方法
- (void)viewDidLoad {
[super viewDidLoad];
//创建节点,displayName是用于提供给周边设备查看和区分此服务的
MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@"KenshinCui_Advertiser"];
_session=[[MCSession alloc]initWithPeer:peerID];
_session.delegate = self;
//创建广播
_advertiserAssistant=[[MCAdvertiserAssistant alloc]initWithServiceType:@"cmj-stream" discoveryInfo:nil session:_session];
_advertiserAssistant.delegate = self;
}
#pragma mark - UI事件
- (IBAction)advertiserClick:(UIBarButtonItem *)sender {
//开始广播
[self.advertiserAssistant start];
}
- (IBAction)selectClick:(UIBarButtonItem *)sender {
//发送文字
//发送数据给所有已连接设备
NSError *error=nil;
[self.session sendData:[@"蓝牙数据传输" dataUsingEncoding:NSUTF8StringEncoding] toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error];
NSLog(@"开始发送数据...");
if (error) {
NSLog(@"发送数据过程中发生错误,错误信息:%@",error.localizedDescription);
} } #pragma mark - MCSession代理方法
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSLog(@"didChangeState");
switch(state){
case MCSessionStateConnected:
NSLog(@"连接成功.");
break;
case MCSessionStateConnecting:
NSLog(@"正在连接...");
break;
default:
NSLog(@"连接失败.");
break;
}
}
@end

再看一下作为发现节点的程序:

#import "ViewController.h"
#import <MultipeerConnectivity/MultipeerConnectivity.h> @interface ViewController ()<MCSessionDelegate, MCBrowserViewControllerDelegate,
UIPopoverControllerDelegate>
@property (strong,nonatomic) MCSession *session;
@property (strong,nonatomic) MCBrowserViewController *browserController;
@property (strong, nonatomic) IBOutlet UILabel *label;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
//创建节点
MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@"KenshinCui"];
//创建会话
_session=[[MCSession alloc]initWithPeer:peerID];
_session.delegate=self;
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} #pragma mark- UI事件
- (IBAction)browserClick:(UIButton *)sender {
_browserController=[[MCBrowserViewController alloc]initWithServiceType:@"cmj-stream" session:self.session];
_browserController.delegate=self; [self presentViewController:_browserController animated:YES completion:nil];
} #pragma mark - MCBrowserViewController代理方法
-(void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController{
NSLog(@"已选择");
[self.browserController dismissViewControllerAnimated:YES completion:nil];
}
-(void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{
NSLog(@"取消浏览.");
[self.browserController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - MCSession代理方法
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSLog(@"didChangeState");
switch (state) {
case MCSessionStateConnected:
NSLog(@"连接成功.");
[self.browserController dismissViewControllerAnimated:YES completion:nil];
break;
case MCSessionStateConnecting:
NSLog(@"正在连接...");
break;
default:
NSLog(@"连接失败.");
break;
}
}
//接收数据
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSLog(@"开始接收数据...");
//接收文字信息
NSLog(@"%@", [NSThread currentThread]);//(<NSThread: 0x170270540>{number = 3, name = (null)}) dispatch_async(dispatch_get_main_queue(), ^{
NSString *string = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.label.font = [UIFont systemFontOfSize:14.0];
self.label.text = string;
});
}
@end

MultipeerConnectivity传输数据三种方式:

通过MultipeerConnectivity传输数据有三种方式分别为:

  • Messages

    Messages使用-sendData:toPeers:withMode:error::方法发送。

  • Streams

    Streams 使用 -startStreamWithName:toPeer:创建:

  • Resources

    Resources 发送使用 -sendResourceAtURL: withName:toPeer: withCompletionHandler:


Messages(普通数据传输)

  • 普通数据的发送方式:
    NSError *error=nil;
NSData *data = [[NSString stringWithFormat:@"--我们要显示的信息--"] dataUsingEncoding:NSUTF8StringEncoding];
[self.session sendData:data toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error];
if (error) {
NSLog(@"发送数据过程中发生错误,错误信息:%@",error.localizedDescription);
}
  • 普通数据的接收方式

    所有数据的传输都在session(会话)中进行。因此,接收函数为session的回调函数。这里需要注意一下,此回调函数不是在主线程中进行的,所以我们如果要改变UI的话,需要转化到主线程。

-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
//NSLog(@"%@", [NSThread currentThread]);//(<NSThread: 0x170270540>{number = 3, name = (null)}) dispatch_async(dispatch_get_main_queue(), ^{
NSString *string = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
self.textView.text = string;
});
}

Streams(数据流传输)

数据流传输可以用在音频播放传输。

  • 数据流基本概念介绍:

什么是数据流?

流是位数据通过通信路径的连续传送序列。它是单向的,从一个应用程序的角度,流可以是输入流(读操作流)或者输出流(写操作流),除了基于文件的流之外,其余的都是non-seekable的。一旦流数据被提供或者被使用,数据就不能够从流中获取到。

-

数据流的简单分类? Cocoa包括三种与流有关的类:NSStream,NSInputStream,NSOutputStream.

NSStream是抽象类,它定义了流对象的基本接口和属性。NSInputStream和NSOutputStream是NSStream的子类,它们实现了输入流和输出流的基本操作。你可以为存储在内存中,向文件或者C

buffer写的流数据创建NSOutputStream对象;可以为从NSData对象和文件中读取的流数据创建NSInputStream对象;也可以在网络套接字的两端创建NSInputStream和NSOutputStream对象,通过流对象,你可以不用一次性将所有的流数据加载到内存中。

-

输入数据流(NSInputStream)的使用步骤?

1. 从数据源创建和初始化一个NSInputStream实例

2. 将输入流对象配置到一个run loop,open the stream

3. 通过流对象的delegate函数处理事件

4. 当所有数据读完,进行流对象的内存处理

-

输出数据流(NSOutputStream)的使用步骤?

1. 使用存储写入数据的存储库创建和初始化一个NSOutputSteam实例,并且设置它的delegate。

2. 将这个流对象布置在一个runloop上并且open the stream。

3. 处理流对象向其delegate发送的事件消息。

4. 如果流对象向内存中写入了数据,那么可以通过使用NSStreamDataWrittenToMemoryStreamKey属性获取数据。

5. 当没有数据可供写入时,清理流对象。

了解学习数据流可以参考:

http://blog.csdn.net/CrazyChickOne/article/details/37565317

  • 数据流的发送方式
    NSError *error;
self.outputStream = [self.session startStreamWithName:@"super stream" toPeer:[self.session.connectedPeers firstObject] error:&error];
self.outputStream.delegate = self;
[self.outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
if(error || !self.outputStream) {
NSLog(@"%@", error);
}
else{
[self.outputStream open];
}
  • 数据流的接收方式
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{
NSLog(@"获取流数据");
self.inputStream = stream;
self.inputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
}
  • 数据流代理操作
/**
* 流数据操作
*
* @param aStream 流数据
* @param eventCode 流数据获取事件
*/
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode) {
case NSStreamEventOpenCompleted:{
NSLog(@"数据流开始");
self.byteIndex = 0;
self.streamData = [[NSMutableData alloc]init];
}
break;
case NSStreamEventHasBytesAvailable:{
//有数据可用
NSInputStream *input = (NSInputStream *)aStream;
uint8_t buffer[1024];
NSInteger length = [input read:buffer maxLength:1024];
NSLog(@"%ld", length);
[self.streamData appendBytes:(const void *)buffer length:(NSUInteger)length];
// 记住这边的数据陆陆续续的
}
break;
case NSStreamEventHasSpaceAvailable:{
//有空间可以存放
NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:[self recordPath]]];
NSOutputStream *output = (NSOutputStream *)aStream;
NSUInteger len = ((data.length - self.byteIndex >= 1024) ? 1024 : (data.length-self.byteIndex));
NSData *data1 = [data subdataWithRange:NSMakeRange(self.byteIndex, len)];
[output write:data1.bytes maxLength:len];
self.byteIndex += len;
}
break;
case NSStreamEventEndEncountered:{
//结束
[aStream close];
[aStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
if([aStream isKindOfClass:[NSInputStream class]]){
NSFileManager *fileManager = [[NSFileManager alloc]init];
[fileManager createFileAtPath:[self recordPath] contents:self.streamData attributes:nil];
}
self.byteIndex = 0;
}
break;
case NSStreamEventErrorOccurred:{
//发生错误
NSLog(@"error");
}
break;
default:
break;
}
}

Resources(数据源传输)

在传输比较大型的文件时,我们通常使用数据源传输。

    NSURL *fileURL = [NSURL fileURLWithPath:[self imagePath]];
self.imageProcess = [self.session sendResourceAtURL:fileURL withName:@"image" toPeer:[self.session.connectedPeers firstObject] withCompletionHandler:^(NSError *error) {\
if (error) {
NSLog(@"发送源数据发生错误:%@", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
//数据传送完毕
[self.progressViewImage setProgress:0 animated:NO];
});
}];
//KVO观察
[self.imageProcess addObserver:self
forKeyPath:@"completedUnitCount"
options:NSKeyValueObservingOptionNew
context:nil];
  • 数据源传输的接受方式

    数据传输都是在session的代理方法中进行的。数据源传送方式有两个代理方法,分别为数据源传输刚刚开始调用session: didStartReceivingResourceWithName: fromPeer: withProgress:和数据源传输结束时调用session: didFinishReceivingResourceWithName: fromPeer: atURL: withError:

    数据源传输刚刚开始调用:一般用于设置一些初始值,比如文件接受者的进度Progress进度KVO观察。

    数据源传输结束时调用:主要用于将传输的文件从暂时存放的位置放到真正需要存放的位置。

  • 数据源传输刚刚开始调用

- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{
NSLog(@"开始获取文件数据");
self.imageProcess = progress;
//KVO观察
[self.imageProcess addObserver:self
forKeyPath:@"completedUnitCount"
options:NSKeyValueObservingOptionNew
context:nil];
}
  • 数据源传输结束时调用
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{
NSLog(@"获取文件数据结束");
NSURL *destinationURL = [NSURL fileURLWithPath:[self imagePath]];
//判断文件是否存在,存在则删除
if ([[NSFileManager defaultManager] isDeletableFileAtPath:[self imagePath]]) {
[[NSFileManager defaultManager] removeItemAtPath:[self imagePath] error:nil];
}
//转移文件
NSError *error1 = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:localURL toURL:destinationURL error:&error1]) {
NSLog(@"[Error] %@", error1);
}
}
  • KVO键值观察
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// NSLog(@"keyPath:%@", keyPath);
// NSLog(@"object:%@", object);
// NSLog(@"change:%@", change);
// NSLog(@"context:%@", context);
dispatch_async(dispatch_get_main_queue(), ^{
int64_t numberCom = self.imageProcess.completedUnitCount;
int64_t numberTotal = self.imageProcess.totalUnitCount;
NSLog(@"%lld/%lld", numberCom, numberTotal);
});
}

参考网站:

iOS7新技术:如何使用Multipeer Connectivity

NSStream

iOS开发--通过MultipeerConnectivity完成蓝牙通讯的更多相关文章

  1. iOS开发系列--通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总

    --系统应用与系统服务 iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用 ...

  2. iOS开发系列通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开

    --系统应用与系统服务 iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用 ...

  3. iOS开发系列--通讯录、蓝牙、

    iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用系统应用.使用系统服务: ...

  4. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  5. iOS开发检测是否开启定位、是否允许消息推送等权限

    1.iOS开发检测是否开启定位: 需要导入: #import <CoreLocation/CoreLocation.h> 代码如下: + (void)openLocationService ...

  6. iOS开发之蓝牙通讯

    iOS开发之蓝牙通讯 一.引言 蓝牙是设备近距离通信的一种方便手段,在iPhone引入蓝牙4.0后,设备之间的通讯变得更加简单.相关的蓝牙操作由专门的CoreBluetooth.framework进行 ...

  7. iOS开发——高级技术&蓝牙服务

    蓝牙服务 蓝牙 随着蓝牙低功耗技术BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙4.0,相比之前的蓝牙技术耗电量大大降低.从iOS的 ...

  8. iOS蓝牙开发(二)蓝牙相关基础知识

    原文链接: http://liuyanwei.jumppo.com/2015/07/17/ios-BLE-1.html iOS蓝牙开发(一)蓝牙相关基础知识: 蓝牙常见名称和缩写 MFI ====== ...

  9. iOS蓝牙开发(一)蓝牙相关基础知识(转)

    转载自:http://www.cocoachina.com/ios/20150915/13454.html 原文作者:刘彦玮 蓝牙常见名称和缩写 MFI ======= make for ipad , ...

随机推荐

  1. C# Json反序列化处理

    最近换工作了 从客户端转到Web端 第一个任务就是去别人的页面上抓取数据 用到的是JSON 因为他们网站json的格式有点怪 所以 就在JSON反序列化上面 花了一点时间 首先用到的工具是http:/ ...

  2. 典当行以及海尔java小节

    1.视图问题,发现jar包都出现在根目录下面了,非常不方便.结果如下表: 原始视图是JavaEE,切换到Java视图即可: 2.Tomcat编译的时候什么都没有加载,看到的是一堆红字,那是因为tomc ...

  3. Spring MVC 统一异常处理

    Spring MVC 统一异常处理 看到 Exception 这个单词都心慌 如果有一天你发现好久没有看到Exception这个单词了,那你会不会想念她?我是不会的.她如女孩一样的令人心动又心慌,又或 ...

  4. dispatch_semaphore

    dispatch_semaphore 信号量基于计数器的一种多线程同步机制.在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题. dispatch_queue_t queue = di ...

  5. 为ant指定编译版本

    用Eclipse的ant折腾了一天也没搞清楚为什么同样的设置ant出的class版本却不一样.后来下载个ant工具在命令行执行通过. 从网上抄得指定编译版本的方法如下: ant 运行时,必需依赖jdk ...

  6. [topcoder]UnsealTheSafe

    http://community.topcoder.com/stat?c=problem_statement&pm=4471&rd=10711 这题果然是道简单题,图+DP.拿道题便觉 ...

  7. VC6.0 list sort出错

    在STL中,排序是个很重要的话题. 1.algorithm 里的sort()只接收RandomAccessIterator用于像vector,dequeue的排序 2.像set,map,这种关联式容器 ...

  8. git日志的查看与修改

    1.命令行中查看日志 git log 默认是显示所有的日志信息,之前出来的界面显示的日志,很少. 最后发现,只需要使用键盘上向下键↓,就可以继续浏览更多的日志 空格键,可以翻页浏览日志. 向左←  向 ...

  9. Linux Kernel空指针引用本地拒绝服务漏洞(CVE-2013-5634)

    漏洞版本: Linux kernel 漏洞描述: BUGTRAQ ID: 61995 CVE(CAN) ID: CVE-2013-5634 Linux Kernel是Linux操作系统的内核. 适用于 ...

  10. Node.js权威指南 (3) - Node.js基础知识

    3.1 Node.js中的控制台 / 19 3.1.1 console.log方法 / 19 3.1.2 console.error方法 / 20 3.1.3 console.dir方法 / 21 3 ...