你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> Hello Bonjour!實現零配置網絡聯網的解決方案

Hello Bonjour!實現零配置網絡聯網的解決方案

編輯:IOS開發基礎

8430890865_252364fda0_b.jpg

一開始用Bonjour,我是拒絕的。

讓我們以一個問題開頭:如何能在本地網絡找到自己想要的硬件設備及相應服務,並連接?

在這個以IP協議進行通信的互聯網世界,我們的計算機與移動設備都需要一個唯一IP作為標示。假如我們有一台打印機需要進行連接。我們必須給他分配一個固定的IP,我們才能夠連接它。然後我們可能還需要去配置一個DNS服務器,這樣子我們才不需要直接通過IP去連接打印機。

很多時候,我們希望可以在本地網絡獲得可用服務的列表,而不需要知道每個服務的IP地址,端口。但是我們不想去經過復雜繁瑣的網絡配置後才能直接獲取本地網絡在線的服務。

讓我們來通過一個真實的項目來逐步探索。就以自己改造的spider,一個調試工具為例。

簡單介紹一下項目對本地網絡連接的需求:我們需要電腦發現本地網絡所有可供調試的iOS設備。

那我們如何進行連接呢。

1. 首先我們想到的最簡單的方法就是通過IP連接。

OK,問題解決。打道回府啦。笑。

spider剛開始的確是用這樣的模式進行的。如果一個人的手機出現了問題,他需要首先在手機上找到IP,然後告訴調試人員。然後調試人員再在網頁輸入IP進行連接。

作為一個有節操的程序員。我們意識到這不是一個優雅的方式。我們想有沒有一種方法能夠獲取本地網絡內需要調試的設備IP呢?

2. UDP廣播。

然後我們想到進行UDP廣播,利用UDP廣播我們能向同一本地網絡所有設備發送一條消息,接收到信息後我們能夠解析出發送方的端口和IP。我們就從這裡開始著手吧。

然後我們開始設計通信協議。為了簡化。我們直接用NSString來傳遞信息,為了分辨出不同設備,於是附上了設備的名字。然後對於iOS Simulator。我們還要另外附上IP以供辨識。最後用NSTimer不斷廣播信息。在這裡我們省略Socket等實現。只貼出發送信息的代碼:

-(void) broadcast
{
NSString *deviceName = [UIDevice currentDevice].name;
if ([deviceName rangeOfString:@"Simulator"].location != NSNotFound ) {
    NSString *appendString = [NSString stringWithFormat:@" %@",[SSNetworkInfo currentIPAddress]];
    deviceName = [deviceName stringByAppendingString:appendString];
}
[self.m_broadcastSocket sendBroadcast:[deviceName dataUsingEncoding:NSUTF8StringEncoding]];
}

局限:

設備端的問題:

  1. 耗電問題。不斷的UDP Broadcast 會耗費大量電源。

  2. 網絡阻塞問題。因為UDP廣播會對同一本地網絡的所有Host都發送信息。過於密集的發送,有可能會造成網絡的堵塞。

  3. 穩定性,UDP Socket可能會因為各種情況而中斷。

接著在電腦端。我們需要考慮的問題:

  1. 應用是否處於Active狀態。我們不能檢測處於Background,Inactive,Suspended狀態的iOS程序。

  2. 設備離線,設備重啟,應用重新編譯等不可連接調試的情況。

  3. 電腦切換Wifi情況,我們只能檢測處於同一網絡的設備。

  4. 設備IP變化,因為設備的IP不是固定的,而是由DHCP服務器動態分配的。很有可能我們要連接的時候,設備的IP已經改變了。

  5. .....

隨著測試和不斷改進,我們發現我們不斷發現問題,不斷增添新的代碼。而且基於Socket實現,我們還要考慮網絡的穩定性,Socket斷開與重連等情況。而且由於UDP廣播的間隔時間與不穩定性,導致我們獲取設備的速度不快和穩定不足。一個簡單的需求逐漸演化為一個小系統。

於是我們想找一種簡潔穩定的協議來替代UDP。Yes,最終我們需要的就是Bonjour。

擁抱Bonjour

什麼是Bonjour?

bonjour來自法語,是你好的意思。一個很有意思的單詞。

bonjour是蘋果公司發布的一個基於ZEROCONF工作組(IETF下屬小組)的工作,用於實現零配置網絡聯網的解決方案。Bonjour是基於IP層協議的。

大家不要被這些高大上的互聯網標准小組,零配置網絡這些名詞嚇到。通俗理解就是這些很棒的組織想要發明一個解決不需要復雜配置就能夠讓本地網絡的設備互相發現彼此的一個方案。

為了實現零配置網絡,需要解決三個需求。

  1. 尋址(分配IP地址給主機)

  2. 命名(使用名字而不是IP地址來代表主機)

  3. 服務搜索(自動在網絡搜索服務)

我們來看看Bonjour的三項主要功能能夠如何解決我們的問題。

1. 尋址

一個在網絡中的設備需要有一個自己的IP。有了IP地址,我們才能基於IP協議進行通信。

實現原理: Bonjour協議的尋址依賴於IP層協議。

對於IPV6標准,IP協議已經包括了自動尋找IP地址的功能。但是目前仍然普遍使用的IPV4 不包含本地鏈路尋址功能。那麼解決方案就是在本地網絡選擇一個隨機的IP地址進行測試,如果已經被占用,則繼續挑選另外一個地址。

blob.png

2. 命名。

我們不想通過冷冰冰的IP地址來作為我們服務的標志。我們想為我們的服務取一個名字。就像打印機一樣,我們希望能在網絡發現它的時候,是以一個比如“二樓的打印機”這樣的標志,而不是一串冷冰冰“10.9.166.45”的IP地址。

就像我們希望發現我們的需要調試的iOS設備的時候,能夠知道它是“Mango's iPhone7”、因此,我們需要給我們的設備和服務命名。

我們還希望能夠通過名字找到服務准確的IP地址,就像在浏覽器輸入"www.qq.com"一樣,DNS服務器會自動幫我導向正確的網站IP地址。

而Bonjour,正是幫我們實現了命名和解析的功能。保證了我們服務的名字在本地網絡是唯一的,並且把別人對我們名字的查詢指向正確的IP地址和端口。

實現原理:

我們在這裡拋開復雜的RFC 6762規范,用簡潔的語言介紹一下原理。

  • 指定名字:

用戶在注冊一個名字的時候,設備向本地網絡發送查詢來確定名字是否選中。如果用戶提供的名字已經被使用,則Bonjour會自動重命名我們的服務。例如我們注冊名字為"Mango's iPhone7"已經被使用,那麼Bonjour可能會幫我們取"Mango's iPhone7-1"的名字。

blob.png

  • 解析名字:

如果有用戶發出一個查詢,說我想找名字叫"Mango's iPhone7"的設備,則本地網絡收到請求的設備看看自己是不是被請求了,如果是的話,則返回正確的IP地址,端口。

blob.png

  • responder

需要了解的是而Bonjour在系統級別上添加了一個mDNSResponder服務來處理請求和發送回復,從系統級層面上處理,我們就無需在應用內自己監聽和返回IP地址,只需到系統內注冊服務即可。減少了我們應用的工作量和提高了穩定性。就好像APNS在iOS上幫助我們維持一個系統級別的長連接。

如果我們打開OS X上的活動監視器,查看目前正在運行的進程,你會發現有mDNSResponder在運行: 

blob.png

PS: 你可能會好奇這些請求和解析是通過什麼進行的,在這裡為了避免加入過多底層網絡細節導致混淆,我只會悄悄的告訴你,背後是通過mDNS協議來進行的。

3. 服務搜索

我們還需要搜索網絡上可用的設備和服務來查看可用的服務。Bonjour幫助我們,只需指定所需服務的類型即可收到本地網絡上可用的設備列表。

實現原理:

設備在本地網絡發出請求,說我需要"XXX"類型的服務,例如:我要打印機服務。所有打印機服務的設備回應自己的名字。

blob.png

bonjour減少功耗的原理:

在spider中,為了節能,我們每隔15s發送一次UDP廣播,但其實還是嚴重耗費資源。

無需服務器的尋址,命名,服務搜索有可能會產生大量的網絡流量。但是Bonjour采取了一些機制來降低零配置的開銷。包括緩存、禁止重復響應,指數回退和服務公告。

1. 緩存(Caching)

Bonjour通過緩存記錄來防止主機請求那些已請求過的信息。例如,當一個主機請求一個打印服務的列表時,收到列表後,本地網絡的主機都會緩存這個列表。下次本地網絡中的一個主機需要打印服務列表時,不需要再次發起請求,因為它已經擁有這個列表的緩存。系統負責維護這個緩存。應用開發者不需要做任何事情來維護它。

2. 阻止重復響應(Suppression of Duplicate Responses)

為了阻止重復響應相同的請求,我們的request會包含了一個已被查詢過的服務的列表。例如,如果主機正在查詢打印機,第一個request不包含任何已查詢過的打印服務,接著我們得到12個打印服務的回復。下一次該主機查詢打印服務時,因為我們已經知道哪些服務被請求過了,已經查詢過的打印服務則不做響應。

Bonjour還以另一種方式來組織重復響應。如果一個主機將要響應,但發現另一個主機已經響應了相同的信息,則主機會阻止它的響應。應用開發者同樣不需要做任何事情來阻止重復響應,由Bonjour來進行管理。

3. 指數回退和服務公告(Exponential Back-off and Service Announcement)

當主機浏覽服務時,它不會不間斷地發送查詢來查看是否有新的服務。相反,主機會初始一個查詢,後續會不斷增加查詢時間的間隔,如1s, 3s, 9s, 27s這樣一個時長間隔,最後可能會長達1小時的間隔。

但這不意味著等一個小時的間隔後再來查看新的服務。當在網絡中啟動一個服務時,它會使用幾次回退算法(也就是類似查詢,1s, 3s, 9s, 27s...這樣的時間間隔)來主動通知它的存在。這樣就將服務公告和搜索的網絡流量保持在最小,而新的服務也會很快就知曉。

在一個運行在已配置Bonjour的主機上的服務,在注冊到mDNSResponder後台駐留程序時會自動發出公告。而運行在其它硬件上的服務,如打印機,需要使用指數回退算法來公告其存在,這樣能充分利用Bonjour的優勢。

最後讓我們來對比一下UDP Broadcast 與 Bonjour的實現相同服務的情況。

blob.png

使用Bonjour

說了這麼多來龍去脈,讓我們直接開始看看如何使用Bonjour:

首先我們看看Bonjour在Cocoa世界裡的實現Stack:

blob.png

API級別:

blob.png

我們直接使用抽象最高級的NSNetService,NSNetServiceBrowser來解決問題。

NSNetService代表一個服務。NSNetServiceBrowser用於搜索服務。

Bonjour有三種最重要的操作。我們在這裡以spider為例子選取最核心的步驟介紹。

1. 發布服務。

在spider中,我們需要裝有測試應用的設備被別人發現。因此我們需要注冊發布到本地網絡上:

      self.netService = [[NSNetService alloc]initWithDomain:@"local." type:@"_spider._tcp." name:@"" port:2333];
    self.netService.delegate = self;
    [self.netService publish];

我們來介紹一下幾個重要的參數:

  • domain :設置service的domain,為了注冊到本地網絡,我們需要使用@".local"

  • type: 指定服務的類型,需要遵循這樣的格式:_ServiceType._TransportProtocolName

  • name: 主機名,如果指定@"",系統會使用當前計算機的名字。

  • port: service運行的端口。

2. 搜索服務。

在spider中,我們需要搜索提供測試的服務設備:

self.browser = [[NSNetServiceBrowser alloc]init];
self.browser.delegate = self;
[self.browser searchForServicesOfType:@"_spider._tcp." inDomain:@"local."];

我們只需指定服務類型與domain即可。

如果NSNetServiceBrowser搜索到服務,則會通知delegate進行處理,我們在這裡進行增刪和更新UI。

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser     didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{
    [self.services addObject:aNetService];
    if (!moreComing) {
     [self.tableView reloadData];
    } 
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
{   
    [self.services removeObject:aNetService];
    if (!moreComing) {
     [self.tableView reloadData];
     }
}

設置正確後,我們就能獲得服務的列表: 

blob.png

3. 解析

獲取service之後,我們並不能直接獲取服務的IP地址,端口和hostName,這是因為隨著時間改變,服務的地址可能會經常改變。因此我們在真正要連接服務的時候進行解析以獲取正確信息。

在spider中,我們選中設備後,需要獲取設備的hostName以進行連接:

- (void)setService:(NSNetService *)service
{
_service = service;
self.service.delegate = self;
[self.service resolveWithTimeout:5];
}

解析完畢後,service會通知delegate:

- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
    //get service's hostName
    NSString *host = sender.hostName;
    //get address data 
     NSData *address = sender.addresses.firstObject;
}

根據不同應用,我們可能獲取Service的IP,端口等提供給Socket, NSURLConnection,網頁使用。這些信息都可以在解析後的service中取得。

需要注意的是我們獲取得到的addrees data是一個C 結構體,我們需要自己取出我們想要的IP地址

這裡給出一個獲取IP地址方法給大家參考:

 (NSString*)IPFromData:(NSData*)data
{
 struct sockaddr_in *addr = (struct sockaddr_in*)[data bytes];
 if (addr->sin_family == AF_INET) {
     NSString *ip = [NSString stringWithFormat:@"%s",inet_ntoa(addr-    >sin_addr)];
   return ip;
 }
 return @"no ip address";
}

總結

我們從需求入手,逐步探索。最終學習Bonjour的原理和使用。Bonjour作為一個系統級別的解決方案,其思路與方案值得我們學習。以後遇到有類似的應用環境時,我們便可以想到我們可愛的Bonjour協議。

Hello,Bonjour!

參考文獻:

  • NSNetServices and CFNetServices Programming Guide

  • Bonjour Overview


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved