MultipeerConnectivity框架,近场通信的基本使用
Multipeer connectivity是一個使附近設備通過Wi-Fi網絡、P2P Wi-Fi以及藍牙個人局域網進行通信的框架。互相鏈接的節點可以安全地傳遞信息、流或是其他文件資源,而不用通過網絡服務。此框架是在iOS7以后推出,旨在替代GameKit下的GKPeerPickerController通信。通過此框架我們可以直接連接同一網絡下的設備,讓其直接進行類似微信,qq那樣的即時通訊效果。
原理
其中通訊的原理,是利用節點來進行廣播服務(標示符),其他節點可以通過服務(標示符)發現廣播。并對此節點進行連接。在項目中可以將廣播和發現放在一起實現,這樣既可以發現并連接到其他節點,同時也可以被其他節點所搜索鏈接。服務的命名規則為由ASCII字母、數字和“-”組成的短文本串,最多15個字符。通常,一個服務的名字應該由應用程序的名字開始,后邊跟“-”和一個獨特的描述符號。
相關類
針對于近場通信,在Multipeer connectivity框架中我們所需要學習的類如下:
1.MCPeerID //代表用戶信息
2.MCSession //啟用和管理Multipeer連接會話中的所有人之間的溝通。 通過Sesion,給別人發送和讀取數據。
3.MCNearbyServiceBrowser //用于搜索附近的服務端,并可以對搜索到的服務端發出邀請加入某個會話中。
4.MCNearbyServiceAdvertiser //廣播服務可以接收,并處理用戶請求連接的響應。但是,這個類會有回調,告知有用戶要與服務端設備連接,需要自定義提示框,以及自定義連接處理。
5.MCAdvertiserAssistant //廣播服務可以接收,并處理用戶請求連接的響應。沒有回調,會彈出默認的提示框,并處理連接。
6.MCBrowserViewController //用于搜索附近的用戶,是基于MCNearbyServiceBrowse的封裝
使用步驟
MCPeerID,MCSession,MCNearbyServiceAdvertiser,MCNearbyServiceBrowser本文中用這四個類來實現(MCNearbyServiceAdvertiser,MCNearbyServiceBrowserx相對來說更原生態,此處通過這個兩個類來編寫代碼更容易幫助我們理解其內部實現的過程)。
通過MCPeer來生成節點信息,
通過MCNearbyServiceAdvertiser來發送廣播,告訴別人這里有個節點可連接,其他節點想要發現此節點必須
通過MCNearbyServiceBrowser來搜索服務(標示符)來找到發送廣播的節點,并請求連接,
當連接成功后便可以通過MCSession來進行消息的發送和讀取。
為了方便理解筆者此處將程序分為服務端(發送服務)和客戶端(搜索服務)。無論是在服務端還是在客戶端其節點信息的配置和消息池的原理都相同,下面是具體的實現過程
代碼實戰
通用部分
a. 配置服務標示符
1 //近場通訊標識符(相當于頻段號) 2 static NSString * const ServiceType = @"nearByContent";
b.創建節點信息和消息池
在這里我們需要先通過MCPeerID創建節點信息(一般為個人設備信息)。并設置MCSession來控制其數據通信。
1 //創建用戶消息和廣播消息池 2 self.peerID = [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name]; 3 self.session = [[MCSession alloc] initWithPeer:self.peerID]; 4 //配置消息池代理 5 self.session.delegate = self;
  由于MCSession的代理方法較多,筆者會在項目端再做說明,下面來看看每個項目端的實現
服務端實現
a.創建一個服務端項目,在storyBoard中配置如下界面,并將兩個BarButtonItem(廣播,停止廣播)和發送消息按鈕分別實現點擊方法
 1 /**
 2  *  廣播方法
 3  */
 4 - (IBAction)startAdvertiser:(UIBarButtonItem *)sender {
 5     [self.advertiser startAdvertisingPeer];
 6     NSLog(@"開啟廣播");
 7 }
 8 /**
 9  *  停止廣播方法
10  */
11 - (IBAction)stopAdvertiser:(UIBarButtonItem *)sender {
12     [self.advertiser stopAdvertisingPeer];
13     NSLog(@"關閉廣播");
14     //關閉時需要關閉通道
15     [self.writeStream close];
16     [self.readStream close];
17     //從消息循環池中移除
18     [self.writeStream removeFromRunLoop:[NSRunLoop mainRunLoop]  forMode:NSDefaultRunLoopMode];
19     [self.readStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
20     [self.session disconnect];
21 }
22 /**
23  *  消息發送方法
24  */
25 - (IBAction)sendMsg:(UIButton *)sender {
26 }
b.在對應的viewController中配置屬性
1 /** 近場客戶 */ 2 @property (nonatomic, strong) MCNearbyServiceBrowser *browser; 3 /** 近場客戶消息池 */ 4 @property (nonatomic, strong) MCSession *session; 5 /** 個人信息 */ 6 @property (nonatomic, strong) MCPeerID *peerID; 7 /** 輸出流 */ 8 @property (nonatomic, strong) NSOutputStream *writeStream; 9 /** 輸入流 */ 10 @property (nonatomic, strong) NSInputStream *readStream; 11 /** 存放廣播端數組 */ 12 @property (nonatomic, strong) NSMutableArray *dataSource; 13 /** 鏈接狀態文本 */ 14 @property (weak, nonatomic) IBOutlet UILabel *stateLabel; 15 /** 消息顯示文本 */ 16 @property (weak, nonatomic) IBOutlet UILabel *msgLabel; 17 /** 消息編輯框 */ 18 @property (weak, nonatomic) IBOutlet UITextField *msgTextField;
c.創建對應的廣播對象MCNearbyServiceAdvertiser
 1 - (MCNearbyServiceAdvertiser *)advertiser {
 2     if (_advertiser == nil) {
 3         _advertiser = ({
 4             //其中discoveryInfo是展示給Browser端查看的信息可設為nil
 5             MCNearbyServiceAdvertiser *advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID discoveryInfo:nil serviceType:ServiceType];
 6             advertiser.delegate = self;
 7             advertiser;
 8         });
 9     }
10     return _advertiser;
11 }
當創建好廣播對象中只需要在對應的地方開啟廣播或停止廣播即可,如上a.步驟中代碼所示,開啟廣播后若接收到其他節點的鏈接請求會觸發廣播對象的代理方法
 1 /**
 2  *  接收到客戶端要求鏈接消息時調用
 3  *
 4  *  @param advertiser        服務端廣播
 5  *  @param peerID            客戶端信息
 6  *  @param context           請求內容
 7  *  @param invitationHandler 是否接受鏈接回調函數
 8  */
 9 - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL, MCSession * _Nonnull))invitationHandler {
10     //一般服務端不會拒絕鏈接所以此處直接鏈接所有客戶端
11     //同意鏈接并加入廣播組消息池
12     invitationHandler(YES,self.session);
13 }
在同意其加入后服務端的消息池就會開始嘗試和客戶端的消息池建立鏈接。建立鏈接時便會回調MCSession的代理方法
 1 /**
 2  *  消息池連通狀態改變時調用
 3  *
 4  *  @param session 消息池
 5  *  @param peerID  節點信息
 6  *  @param state   消息池連通狀態
 7  */
 8 - (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
 9     switch (state) {
10         case MCSessionStateConnecting:
11             NSLog(@"正在鏈接至:%@",peerID.displayName);
12             break;
13         case MCSessionStateConnected:{
14             NSLog(@"與%@建立鏈接",peerID.displayName);
15             [self.dataSource addObject:peerID];
16             //鏈接成功后創建輸出流
17             NSError *error;
18             self.writeStream = [self.session startStreamWithName:@"adverting" toPeer:peerID error:&error];
19             if (error) {
20                 NSLog(@"輸出流創建失敗");
21             }
22             //將輸出流通道打開,并加入消息循環池
23             [self.writeStream open];
24             [self.writeStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
25             //展示鏈接狀態
26             dispatch_async(dispatch_get_main_queue(), ^{
27                 self.stateLabel.text = @"已連接";
28             });
29         }
30             break;
31         case MCSessionStateNotConnected:{
32             NSLog(@"與%@無連接",peerID.displayName);
33             [self.dataSource removeObject:peerID];
34             dispatch_async(dispatch_get_main_queue(), ^{
35                 self.stateLabel.text = @"未連接";
36             });
37         }
38             break;
39         default:
40             break;
41     }
42 }
d.當消息池狀態為鏈接時便可開始發送消息,發送消息的方法分為3種:
1.直接發送二進制數據
1 //二進制文件傳輸方法 2 [self.session sendData:[self.msgTextField.text dataUsingEncoding:NSUTF8StringEncoding] toPeers:self.dataSource withMode:MCSessionSendDataReliable error:&error];
當消息池接收到消息后會回調MCSession的代理方法
 1 /**
 2  *  接收到二進制數據時調用
 3  *
 4  *  @param session 信息池
 5  *  @param data    二進制數據
 6  *  @param peerID  節點信息
 7  */
 8 - (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
 9     //獲取傳輸數據
10     NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
11     dispatch_async(dispatch_get_main_queue(), ^{
12         //展示數據
13         self.msgLabel.text = text;
14     });
15 }
2.通過輸入輸出流來發送二進制數據
輸出流的創建已在消息池鏈接狀態回調函數中寫出,此處便不再多說,當創建好輸出流后,對應的鏈接的消息池會接受到輸入流的鏈接,其MCSession回調函數為
 1 /**
 2  *  接受到數據流事件請求時調用
 3  *
 4  *  @param session    信息池
 5  *  @param stream     輸入數據流
 6  *  @param streamName 數據流名字
 7  *  @param peerID     節點信息
 8  */
 9 - (void)    session:(MCSession *)session
10    didReceiveStream:(NSInputStream *)stream
11            withName:(NSString *)streamName
12            fromPeer:(MCPeerID *)peerID {
13     //打開請求的輸入流通道,加入消息循環池
14     [stream open];
15     [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
16     //設置代理,以接收數據
17     stream.delegate = self;
18     //持有該輸入流
19     self.readStream = stream;
20 }
當輸入輸出流都鏈接完成并打開通道加入消息循環池后便可以開始利用輸入輸出流來進行數據通信,輸入輸出流的數據通信在前面的博客(http://www.cnblogs.com/purple-sweet-pottoes/p/4856955.html)中已有列出,此處不再贅述。
3.通過文件url地址,直接發送文件
關于文件的發送其實也和前面差不讀,筆者此處直接貼出對應方法
1 //MCSession發送文件方法 2 - (void)sendResourceAtURL:(NSURL *)resourceURL 3 withName:(NSString *)resourceName 4 toPeer:(MCPeerID *)peerID 5 withCompletionHandler:(nullable void (^)(NSError * __nullable error))completionHandler 6 7 //MCSeesion收到文件時的回調方法 8 - (void) session:(MCSession *)session 9 didStartReceivingResourceWithName:(NSString *)resourceName 10 fromPeer:(MCPeerID *)peerID 11 withProgress:(NSProgress *)progress; 12 - (void) session:(MCSession *)session 13 didFinishReceivingResourceWithName:(NSString *)resourceName 14 fromPeer:(MCPeerID *)peerID 15 atURL:(NSURL *)localURL 16 withError:(nullable NSError *)error;
到這里服務端的實現已經基本完成。
客戶端的實現
客戶端的實現路數基本同服務端相同,唯一有所不同的是服務端創建的是廣播對象,而在客戶端是創建搜索服務對象
1 - (MCNearbyServiceBrowser *)browser {
2     if (_browser == nil) {
3         _browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID serviceType:ServiceType];
4         _browser.delegate = self;
5     }
6     return _browser;
7 }
對應將廣播和停止廣播方法改為掃描和斷開服務(由于代碼相同便不再注釋)
 1 - (IBAction)startSearchAdver:(UIBarButtonItem *)sender {
 2     [self.browser startBrowsingForPeers];
 3 }
 4 - (IBAction)stopConnectAdver:(UIBarButtonItem *)sender {
 5     [self.browser stopBrowsingForPeers];
 6     [self.writeStream close];
 7     [self.readStream close];
 8     [self.writeStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
 9     [self.readStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
10     [self.session disconnect];
11 }
當掃描到對應的服務節點后,便會回調掃描對象的代理方法
1 - (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary<NSString *,NSString *> *)info {
2     //請求鏈接到對應的服務節點
3     [browser invitePeer:peerID toSession:self.session withContext:nil timeout:30];
4     NSLog(@"發現%@廣播,正在鏈接...",peerID.displayName);
5 }
其他部分對應服務端代碼來編寫即可!
效果圖:
1.未廣播服務和掃描服務時狀態
2.開啟廣播和開始掃描服務后狀態
3.狀態鏈接便可以開始點對點通信
4.任意端斷開服務后,其消息池都會斷開鏈接,若要重新發送消息需要重新進行鏈接
關于近場通信的基本使用就講到這里,如其中有何錯誤之處請指出,謝謝!最后祝大家新年快樂!
總結
以上是生活随笔為你收集整理的MultipeerConnectivity框架,近场通信的基本使用的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 分布式与人工智能课程(part13)--
- 下一篇: 文献记录(part75)--基于最大平均
