iOS 局域网通讯 MultipeerConnectivity

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
在这里插入图片描述

联系作者

期待你的点赞和关注!如有疑问,联系作者。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_39404995/article/details/113242547