概述
iOS 局域网通讯 MultipeerConnectivity
问题
最近想做一个小游戏demo,需要这个功能:两台设备使用本地局域网进行游戏。
于是查找到iOS实现局域网的API,最终决定用MultipeerConnectivity实现该功能
解决
根据自己使用的效果,简单进行了封装,可以借鉴
demo地址 MultipeerConnectivity-Demo
原理:一个设备创建一个服务器发送广播,另一个设备创建客服端链接,,,链接上后,双方就可以通讯
实现如下
ConnectBaseViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
static NSString * const ServiceType = @"nearByContent";//这个是当前局域网的标识,需要在info.plist里配置
@interface ConnectBaseViewController : UIViewController
-(instancetype)initWithUserName:(NSString*)name;
-(void)startScan;
-(void)stopScan;
-(void)startPush;
-(void)stopPush;
-(void)sendMsg:(NSString *)msg;
@end
NS_ASSUME_NONNULL_END
ConnectBaseViewController.m
#import "ConnectBaseViewController.h"
#import <MultipeerConnectivity/MultipeerConnectivity.h>
@interface ConnectBaseViewController ()<MCSessionDelegate,MCNearbyServiceAdvertiserDelegate,MCNearbyServiceBrowserDelegate,NSStreamDelegate>
@property (nonatomic, strong) MCNearbyServiceAdvertiser *advertiser;
@property (nonatomic, strong) MCNearbyServiceBrowser *browser;
@property (nonatomic, strong) MCSession *session;
@property (nonatomic, strong) MCPeerID *peerID;
@property (nonatomic, strong) NSOutputStream *writeStream;
@property (nonatomic, strong) NSInputStream *readStream;
@property (nonatomic, strong) NSMutableArray<MCPeerID *> *dataSource;
@end
@implementation ConnectBaseViewController
-(instancetype)initWithUserName:(NSString*)name{
self = [super init];
if(self){
[self loadUserInfo:name];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.dataSource = [NSMutableArray array];
// Do any additional setup after loading the view.
}
-(void)loadUserInfo:(NSString *)name{
//创建用户消息和广播消息池
self.peerID = [[MCPeerID alloc] initWithDisplayName:name];
self.session = [[MCSession alloc] initWithPeer:self.peerID];
//配置消息池代理
self.session.delegate = self;
}
/**
* 消息池连通状态改变时调用
*
* @param session 消息池
* @param peerID 节点信息
* @param state 消息池连通状态
*/
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
switch (state) {
case MCSessionStateConnecting:
NSLog(@"正在链接至:%@",peerID.displayName);
break;
case MCSessionStateConnected:{
NSLog(@"与%@建立链接",peerID.displayName);
[self.dataSource addObject:peerID];
//链接成功后创建输出流
NSError *error;
self.writeStream = [self.session startStreamWithName:@"adverting" toPeer:peerID error:&error];
if (error) {
NSLog(@"输出流创建失败");
}
//将输出流通道打开,并加入消息循环池
[self.writeStream open];
[self.writeStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//展示链接状态
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"已连接");
});
}
break;
case MCSessionStateNotConnected:{
NSLog(@"与%@无连接",peerID.displayName);
[self.dataSource removeObject:peerID];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"未连接");
});
}
break;
default:
break;
}
}
/**
* 接收到二进制数据时调用
*
* @param session 信息池
* @param data 二进制数据
* @param peerID 节点信息
*/
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
//获取传输数据
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到%@的消息:%@",peerID.displayName,text);
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
NSLog(@"stream=handleEvent");
}
/**
* 接受到数据流事件请求时调用
*
* @param session 信息池
* @param stream 输入数据流
* @param streamName 数据流名字
* @param peerID 节点信息
*/
- (void) session:(MCSession *)session
didReceiveStream:(NSInputStream *)stream
withName:(NSString *)streamName
fromPeer:(MCPeerID *)peerID {
//打开请求的输入流通道,加入消息循环池
[stream open];
[stream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//设置代理,以接收数据
stream.delegate = self;
//持有该输入流
self.readStream = stream;
NSLog(@"stream=didReceiveStream");
}
// Start receiving a resource from remote peer.
- (void) session:(MCSession *)session
didStartReceivingResourceWithName:(NSString *)resourceName
fromPeer:(MCPeerID *)peerID
withProgress:(NSProgress *)progress{
NSLog(@"3333");
}
// Finished receiving a resource from remote peer and saved the content
// in a temporary location - the app is responsible for moving the file
// to a permanent location within its sandbox.
- (void) session:(MCSession *)session
didFinishReceivingResourceWithName:(NSString *)resourceName
fromPeer:(MCPeerID *)peerID
atURL:(nullable NSURL *)localURL
withError:(nullable NSError *)error{
NSLog(@"2222");
}
- (MCNearbyServiceAdvertiser *)advertiser {
if (_advertiser == nil) {
//其中discoveryInfo是展示给Browser端查看的信息可设为nil
_advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID discoveryInfo:nil serviceType:ServiceType];
_advertiser.delegate = self;
}
return _advertiser;
}
- (MCNearbyServiceBrowser *)browser {
if (_browser == nil) {
_browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID serviceType:ServiceType];
_browser.delegate = self;
}
return _browser;
}
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary<NSString *,NSString *> *)info {
//请求链接到对应的服务节点
[browser invitePeer:peerID toSession:self.session withContext:nil timeout:30];
NSLog(@"发现%@广播,正在链接...",peerID.displayName);
}
/**
* 接收到客户端要求链接消息时调用
*
* @param advertiser 服务端广播
* @param peerID 客户端信息
* @param context 请求内容
* @param invitationHandler 是否接受链接回调函数
*/
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL, MCSession * _Nonnull))invitationHandler {
//一般服务端不会拒绝链接所以此处直接链接所有客户端
//同意链接并加入广播组消息池
NSLog(@"%@申请接入",peerID.displayName);
invitationHandler(YES,self.session);
}
// A nearby peer has stopped advertising.
- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID{
}
-(void)startScan{
NSLog(@"开始扫描");
[self.browser startBrowsingForPeers];
}
-(void)stopScan{
NSLog(@"结束扫描");
[self.browser stopBrowsingForPeers];
[self.writeStream close];
[self.readStream close];
[self.writeStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.readStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.session disconnect];
}
-(void)startPush{
NSLog(@"开启广播");
[self.advertiser startAdvertisingPeer];
}
-(void)stopPush{
NSLog(@"关闭广播");
[self.advertiser stopAdvertisingPeer];
//关闭时需要关闭通道
[self.writeStream close];
[self.readStream close];
//从消息循环池中移除
[self.writeStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.readStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.session disconnect];
}
-(void)sendMsg:(NSString *)msg{
//二进制文件传输方法
[self.session sendData:[msg dataUsingEncoding:NSUTF8StringEncoding] toPeers:self.dataSource withMode:MCSessionSendDataReliable error:nil];
}
@end
使用
服务器
ServiceViewController * serviceVc = [[ServiceViewController alloc]initWithUserName:@"service"];
[self presentViewController:serviceVc animated:YES completion:^{
//[serviceVc startPush];
//[serviceVc stopPush];
//[serviceVc sendMsg:@"这是一条来自服务器的消息"];
}];
客户端
ClientViewController * clientVc = [[ClientViewController alloc]initWithUserName:@"client"];
[self presentViewController:clientVc animated:YES completion:^{
}];
//[clientVc startScan];
//[clientVc stopScan];
//[clientVc sendMsg:@"这是一条来自客户端的消息"];
遇到的问题及解决方案
问题:报错Server did not publish
[MCNearbyServiceAdvertiser] Server did not publish: errorDict [{
NSNetServicesErrorCode = "-72008";
NSNetServicesErrorDomain = 10;
}].
解决:在info里配置NSBonjourServices信息
添加_nearByContent._tcp(nearByContent这个string是你工程设置的局域网标识,就是ConnectBaseViewController.h里的ServiceType)
提示:最好在info.plist申明你使用局域网的目的
NSLocalNetworkUsageDescription
联系作者
期待你的点赞和关注!如有疑问,联系作者。
最后
以上就是香蕉网络为你收集整理的iOS 局域网通讯 MultipeerConnectivity的全部内容,希望文章能够帮你解决iOS 局域网通讯 MultipeerConnectivity所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复