iOS近距离实时通信解决方案

2018 年 3 月 20 日 CocoaChina 1

前言


最近研究iOS设备间的近距离实时通信,对其解决方案进行了解,整理如下:



其中AirDrop常用于iOS/OS X系统间分享图片、视频等,但实时性较差;


CoreBluetooth带宽较小;


GameKit已被弃用;


Socket方案需要iOS设备在同个局域网内;


ExternalAccessory不适用iOS设备间的场景;


MultipeerConnectivity从了解的信息来看,较为符合近距离实时通信的要求,本文便介绍如何使用MultipeerConnectivity框架。


正文


用MultipeerConnectivity进行实时通信分为两步,一是建立二进制流通道,二是进行协议通信。


一、建立流通道


demo需要使用两个iOS设备(手机A和手机B),分别命名为server(手机A)和client(手机B)。同时为了容易学习,demo分为两个工程(server和client),实际开发应是同一份工程,通过不同的Role来区分。


建立流通道的过程如下:


流通道建立过程


1、手机A发起广播


手机A作为server,需要先发起广播。


MCPeerID是连接中表示本设备的标识,长度不能超过63 bytes(UTF-8 编码)。


MCAdvertiserAssistant是广播管理类,提供广播发起接口、广播代理回调。


发起广播需要先创建MCPeerID和MCAdvertiserAssistant。


    MCPeerID *peerId = [[MCPeerID alloc] initWithDisplayName:@"server"];

    self.mSession = [[MCSession alloc] initWithPeer:peerId];

    self.mSession.delegate = self;

     

    self.mAdvertiserAssistant = [[MCAdvertiserAssistant alloc] initWithServiceType:@"connect" discoveryInfo:nil session:self.mSession];

    self.mAdvertiserAssistant.delegate = self;


创建完,就可以调用startServer,发起广播。


- (void)startServer {

    [self.mAdvertiserAssistant start];

}


2、手机B搜索广播


手机B作为client,需要搜索并请求建立连接。建立连接前同样需要创建MCPeerID和MCSession。


    MCPeerID *peerId = [[MCPeerID alloc] initWithDisplayName:@"client"];

    self.mSession = [[MCSession alloc] initWithPeer:peerId];

    self.mSession.delegate = self;


MCBrowserViewController是系统提供的建立连接用的VC,会自动搜索附近的广播并展示在列表中,点击之后即可请求建立连接。


- (void)startClient {

    if (!self.mBrowserVC) {

        self.mBrowserVC = [[MCBrowserViewController alloc] initWithServiceType:@"connect" session:self.mSession];

        self.mBrowserVC.delegate = self;

    }

    [self presentViewController:self.mBrowserVC animated:YES completion:nil];

}


3、手机A接受连接


当手机B请求建立连接之后,手机A会弹出建立连接的请求,如下:



点击Accept,完成连接的建立过程。


连接成功建立之后,MCSession会回调MCSessionStateConnected。


- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {

    if (session == self.mSession) {

        NSString *str;

        switch (state) {

            case MCSessionStateConnected:

                str = @"连接成功.";

                break;

            case MCSessionStateConnecting:

                str = @"正在连接...";

                break;

            default:

                str = @"连接失败.";

                break;

        }

        NSLog(@"id:%@, changeState to:%@", peerID.displayName, str);

    }

}


4、手机A创建输出流


手机A作为server,主动建立输出流。


注意,需要把mOutputStream放入RunLoop,并调用open。


    if (!self.mOutputStream) {

        self.mOutputStream = [self.mSession startStreamWithName:@"delayTestServer" toPeer:[self.mSession.connectedPeers firstObject] error:nil];

        self.mOutputStream.delegate = self;

        [self.mOutputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

        [self.mOutputStream open];

    }


5、手机B接受输入流并创建输出流


手机B作为client,接受server的输出流,并且以此创建client的输出流。


这里有两个注意点:


  • server的输出流,在client的表现为输入流;

  • 下面的回调方法是在子线程,所以加入主线程是[NSRunLoop mainRunLoop],不是[NSRunLoop currentRunLoop];


- (void)    session:(MCSession *)session

   didReceiveStream:(NSInputStream *)stream

           withName:(NSString *)streamName

           fromPeer:(MCPeerID *)peerID {

    if (self.mSession == session) {

        NSLog(@"didReceiveStream:%@, named:%@ from id:%@", [stream description], streamName, peerID.displayName);

        if (self.mInputStream) {

            [self.mInputStream close];

        }

        self.mInputStream = stream;

        self.mInputStream.delegate = self;

        [self.mInputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

        [self.mInputStream open];

    }

}


6、手机A接受输入流


手机A作为server,接受client的输出流,完成流通道的建立。


二、协议通信


在建立完二进制流通道之后,server和client便可进行通信。


通信的基础是Protocal协议,为了简化,协议全部使用Int32。


ProtocolType.h中的简单延迟测试协议如下:

typedef NS_ENUM(int32_t, ProtocolType) {

    ProtocolTypeNone = 0,

    //ProtocolTypeDelay A向B发送一条消息,B立刻返回,A接受到返回的消息,计算两次消息的延迟;

    ProtocolTypeDelayReq = 11,

    ProtocolTypeDelayRsp = 12,

};


整个延迟测试分为三步,手机A向手机B发送一条消息,手机B收到消息之后立刻回包,手机A接收到B的消息,计算整个过程的耗时,可以得到RTT(Round-Trip Time)的大小。


1、手机A发送延迟测试协议req


手机A作为server,主动发起延迟测试。


在发送ProtocolTypeDelayReq协议的时候,还要记录此次发送的时间mDelayStartDate,以便计算延迟。


    int32_t type = ProtocolTypeDelayReq;

    self.mDelayStartDate = [NSDate dateWithTimeIntervalSinceNow:0];

    [self.mOutputStream write:(uint8_t *)&type maxLength:4];


2、手机B接收延迟测试协议req,并立刻回包


手机B作为client,收到消息之后,先解析协议类型。


- (void)onInputDataReady {

    ProtocolType type = 0;

    [self.mInputStream read:(unsigned char *)&type maxLength:sizeof(type)];

    [self handleProtocolWithType:type];

}

当收到ProtocolTypeDelayReq协议时,返回ProtocolTypeDelayRsp协议。


- (void)handleProtocolWithType:(ProtocolType)type {

    if (type == ProtocolTypeDelayReq) {

        int32_t type = ProtocolTypeDelayRsp;

        [self.mOutputStream write:(uint8_t *)&type maxLength:4];

    }

}


3、手机A接收回包,并计算RTT耗时


手机A收到消息,同样进行消息解析。


当收到ProtocolTypeDelayRsp协议时,进行往返耗时计算,得到本次RTT大小。


- (void)handleProtocolWithType:(ProtocolType)type {

    if (type == ProtocolTypeDelayRsp) {

        NSDate *rspDate = [ NSDate dateWithTimeIntervalSinceNow:0];

        NSTimeInterval delay = [rspDate timeIntervalSinceDate:self.mDelayStartDate];

        self.mAverageDelayTime += delay * 1000;

        ++self.mDelayCount;

        NSLog(@"delay test with %.2lfms,  average delay time:%.2lfms", delay * 1000, self.mAverageDelayTime / self.mDelayCount);

    }

}


总结


demo有两处比较有意思的地方,一是MultipeerConnectivity的建立连接过程,二是通信协议的发送和解析。


MultipeerConnectivity建立连接的过程与TCP的三次握手有异曲同工之妙,感觉就很美妙。


通信协议的发送和解析,实质上是二进制流数据的处理。实际开发过程中,会添加更多的协议头、协议尾、校验字段,还有缓冲处理、粘包处理等等有意思的内容。


先写一篇简单的文章介绍MultipeerConnectivity框架,后面再写一篇项目中接入MultipeerConnectivity的实际应用。


demo地址


参考


iOS近场通信(蓝牙开发,WiFi开发)


作者:落影loyinglin

链接:https://www.jianshu.com/p/56e6a67cc214


登录查看更多
0

相关内容

iOS 是苹果公司为其移动产品开发的操作系统。它主要给 iPhone、iPod touch、iPad 以及 Apple TV 使用。原本这个系统名为 iPhone OS,直到2010年6月7日 WWDC 大会上宣布改名为 iOS。
华为发布《自动驾驶网络解决方案白皮书》
专知会员服务
122+阅读 · 2020年5月22日
【Google】利用AUTOML实现加速感知神经网络设计
专知会员服务
28+阅读 · 2020年3月5日
已删除
AI掘金志
7+阅读 · 2019年7月8日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
【边缘计算】边缘计算面临的问题
产业智能官
17+阅读 · 2019年5月31日
镭神多线激光雷达强势赋能许昌芙蓉湖5G自动驾驶示范区
未来产业促进会
10+阅读 · 2019年5月8日
iOS自定义带动画效果的模态框
CocoaChina
7+阅读 · 2019年3月3日
这一次,彻底解决滚动穿透
IMWeb前端社区
34+阅读 · 2019年1月4日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
Android P正式发布,你需要尽快做适配了
前端之巅
3+阅读 · 2018年8月7日
车辆目标检测
数据挖掘入门与实战
30+阅读 · 2018年3月30日
用Python调用百度OCR接口实例
数据挖掘入门与实战
16+阅读 · 2018年1月29日
Arxiv
24+阅读 · 2020年3月11日
Clustered Object Detection in Aerial Images
Arxiv
5+阅读 · 2019年8月27日
q-Space Novelty Detection with Variational Autoencoders
Arxiv
4+阅读 · 2018年10月5日
Arxiv
6+阅读 · 2018年3月19日
Arxiv
7+阅读 · 2018年1月24日
Arxiv
6+阅读 · 2018年1月14日
VIP会员
相关VIP内容
华为发布《自动驾驶网络解决方案白皮书》
专知会员服务
122+阅读 · 2020年5月22日
【Google】利用AUTOML实现加速感知神经网络设计
专知会员服务
28+阅读 · 2020年3月5日
相关资讯
已删除
AI掘金志
7+阅读 · 2019年7月8日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
【边缘计算】边缘计算面临的问题
产业智能官
17+阅读 · 2019年5月31日
镭神多线激光雷达强势赋能许昌芙蓉湖5G自动驾驶示范区
未来产业促进会
10+阅读 · 2019年5月8日
iOS自定义带动画效果的模态框
CocoaChina
7+阅读 · 2019年3月3日
这一次,彻底解决滚动穿透
IMWeb前端社区
34+阅读 · 2019年1月4日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
Android P正式发布,你需要尽快做适配了
前端之巅
3+阅读 · 2018年8月7日
车辆目标检测
数据挖掘入门与实战
30+阅读 · 2018年3月30日
用Python调用百度OCR接口实例
数据挖掘入门与实战
16+阅读 · 2018年1月29日
相关论文
Arxiv
24+阅读 · 2020年3月11日
Clustered Object Detection in Aerial Images
Arxiv
5+阅读 · 2019年8月27日
q-Space Novelty Detection with Variational Autoencoders
Arxiv
4+阅读 · 2018年10月5日
Arxiv
6+阅读 · 2018年3月19日
Arxiv
7+阅读 · 2018年1月24日
Arxiv
6+阅读 · 2018年1月14日
Top
微信扫码咨询专知VIP会员