iOS开发 Bonjour的使用

  1. Bonjour简介
    Bonjour是 Apple推出的零配置网络协议,主要的目的是在缺少中心服务器的情况下解决网络设备的 IP获取,名称解析和服务发现等关键问题。

  2. Bonjour可以做什么
    如上面提到的, Bonjour可以完成的工作主要是在缺少中心服务器的情况下解决 IP获取,名称解析和服务发现这三个问题。

  3. IP获取
    在传统网络环境下,设备的 IP地址通过两种方式获取,一种是静态配置,通过手工方式为设备指定一个 IP地址,一种是动态配置,设备通过路由器的 DHCP服务获得动态的 IP地址。
    在无中心服务器的网络环境下,没有中心服务器提供 DHCP服务,用户手工配置 IP地址也很不方便,这就需要一种新的方式来帮助设备获取 IP地址,就是希望设备可以主动为自己指定一个可用的 IP地址。
    在 IPV6环境下, IPV6协议本身就提供了设备自指定 IP地址的能力,所以实现很简单,直接使用 IPV6的协议支持就可以了。
    在 IPV4环境下, Bonjour使用了随机指定 IP地址的方法,首先为设备随机指定一个属于本地网段的 IP地址,然后检查该地址在本地是否有冲突,如果有冲突就随机生成另一个新的 IP地址,直到找到可用 IP地址为止。
    我在做测试的时候没有测试这部分,都是使用的 DHCP的动态地址。以后有时间测试了这个部分后再和大家分享测试结果。

  4. 名称解析
    在传统网络环境下,名称和 IP地址的对应关系是通过 DNS服务解析的。当一个设备需要访问一个域名,如 “www.abc.com”,设备将 “www.abc.com”发给 DNS服务器,服务器返回该域名对应的 IP地址,设备再使用返回的 IP地址对目标服务器进行访问。
    在没有中心服务器的网络环境中,没有 DNS服务器提供域名解析服务,名称解析变成一个严重问题。针对这一问题,业界的解决方案是 mDNS,中文叫 “组播 DNS”,在标准文档 RFC6762中定义。
    “组播 DNS”的原理很简单,当一个设备需要解析一个名称时,如 “abc.local.”,这个设备通过 UDP协议向本地网络中的所有设备广播一个消息,问谁是 “abc.local”,本地网络中如果有一个设备认为自己是 “abc.local”,它就给出响应,说出自己的 IP地址。
    因为 “组播 DNS”基于 UDP协议,采用广播消息的方式,所以不需要一个中心服务器提供 DNS解析服务就可以完成本地的名称解析。
    Bonjour也是基于 mDNS协议的,不过 Bonjour在 mDNS协议上作了扩展,加强了设备响应 “组播 DNS”请求的能力。在 Bonjour协议下,应用只需要对某个名称进行注册,就可以将响应 “组播 DNS”请求的工作交由底层处理。也就是说在 Bonjour协议下,应用不需要侦听本地网络的 “组播 DNS”请求并进行响应,这些工作由底层系统完成。
    为了区分全球域名和本地域名, mDNS协议使用 “.local.”作为本地域名的根域名。

  5. 服务发现
    当一个提供服务的设备获取 IP地址,并自我指定一个域名后,其实还是不能满足用户的需求。因为用户需要的是某种服务,如打印服务, web服务,用户并不关心这些服务对应的服务器名称和它的 IP地址。
    为了让用户更容易发现本地网络中的各种服务, Bonjour为设备提供了服务发现的能力。
    Bonjour 提供的“ 服务发现” 能力基于一个简单直接的规定,就是提供服务的设备在按以下标准对服务进行注册:“ 名称. 服务类型. 传输协议类型.local.” ,比如:“DamonWebServer._http._tcp.local.” ,又比如“DummiesWebServer._http._tcp.local.” 。
    这样,当一个设备使用希望查找 http服务的时候, Bonjour会去查找本地网络中注册过的包含 "_http"的服务,然后将结果返回给用户选择。这时用户面对的是 “DamonWebServer”和 "DummiesWebServer",用户可以不去关心到底这两个 web服务到底在那台设备上,该设备的 IP地址是什么。

  6. 如何使用Bonjour
    对于最终用户来讲, Bonjour基本上是透明的,他们不需要了解如何去使用 Bonjour,往往都是应用开发者去考虑如何使用 Bonjour。
    对于应用开发者来讲,他们需要考虑有两部分,一是如何作为 Bonjour客户端去发现使用本地服务,二是如何作为服务端如何注册 Bonjour服务

  7. 如何作为Bonjour客户端去发现本地服务
    废话不多说,直接上代码。以下代码可以直接复制粘贴使用。

#import "FindDeviceService.h"
#include <arpa/inet.h>


@interface FindDeviceService ()<NSNetServiceDelegate,NSNetServiceBrowserDelegate>

//定义NSNetService,NSNetServiceBrowser两个变量以及添加代理
@property(strong,nonatomic)NSNetServiceBrowser *brower;

@property(strong,nonatomic)NSNetService *service;

@property(strong,nonatomic)NSMutableArray *dataArray;

@property(assign,nonatomic)NSInteger number;

@end

@implementation FindDeviceService

- (NSMutableArray *)dataArray{
    
    if (_dataArray == nil) {
        _dataArray = [[NSMutableArray alloc]init];
    }
    return _dataArray;
}

/**
 单利模式
 */
+(FindDeviceService *) sharedInstance
{
    static FindDeviceService *sharedInstace = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstace = [[self alloc] init];
    });
    return sharedInstace;
}

- (void)createServiceBrowser{
    
    self.brower = [[NSNetServiceBrowser alloc]init];
    
    self.brower.delegate = self;
    
    [self.brower searchForServicesOfType:@"_raop._tcp" inDomain:@"local."];
}


/*
 * 即将查找服务
 */
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser {
    NSLog(@"-----------------netServiceBrowserWillSearch");
}

/*
 * 停止查找服务
 */
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
    NSLog(@"-----------------netServiceBrowserDidStopSearch");
}

/*
 * 查找服务失败
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict {
    NSLog(@"----------------netServiceBrowser didNotSearch");
    
}

/*
 * 发现域名服务
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindDomain:(NSString *)domainString moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didFindDomain");
}

/*
 * 发现客户端服务
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
    
    NSLog(@"didFindService---------=\n%@  \n=%@  \n=%@",service.name,service.addresses,service.hostName);
        
    self.service = service;
    
    self.service.delegate = self;
    //设置解析超时时间
    [self.service resolveWithTimeout:5.0];
}

/*
 * 域名服务移除
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveDomain:(NSString *)domainString moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didRemoveDomain");
}

/*
 * 客户端服务移除
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didRemoveService %@",service.name);
}

/*
 * 通过NSNetService解析信息
 */
- (NSDictionary *)parsingIP:(NSNetService *)sender{
    int sPort = 0;
    NSString *ipv4;
    NSString *ipv6;
    
    for (NSData *address in [sender addresses]) {
        typedef union {
            struct sockaddr sa;
            struct sockaddr_in ipv4;
            struct sockaddr_in6 ipv6;
        } ip_socket_address;
        
        struct sockaddr *socketAddr = (struct sockaddr*)[address bytes];
        if(socketAddr->sa_family == AF_INET) {
            sPort = ntohs(((struct sockaddr_in *)socketAddr)->sin_port);
            struct sockaddr_in* pV4Addr = (struct sockaddr_in*)socketAddr;
            int ipAddr = pV4Addr->sin_addr.s_addr;
            char str[INET_ADDRSTRLEN];
            ipv4 = [NSString stringWithUTF8String:inet_ntop( AF_INET, &ipAddr, str, INET_ADDRSTRLEN )];
        }
        
        else if(socketAddr->sa_family == AF_INET6) {
            sPort = ntohs(((struct sockaddr_in6 *)socketAddr)->sin6_port);
            struct sockaddr_in6* pV6Addr = (struct sockaddr_in6*)socketAddr;
            char str[INET6_ADDRSTRLEN];
            ipv6 = [NSString stringWithUTF8String:inet_ntop( AF_INET6, &pV6Addr->sin6_addr, str, INET6_ADDRSTRLEN )];
        }
        else {
            NSLog(@"Socket Family neither IPv4 or IPv6, can't handle...");
        }
    }
    
    if ([ipv6 isEqual:[NSNull null]] || ipv6 == nil) {
        ipv6 = @"";
    }
    
    if ([ipv4 isEqual:[NSNull null]] || ipv4.length == 0) {
        ipv4 = @"";
    }
    
    NSDictionary *data = @{@"type": [sender type],
                           @"domain":[sender domain],
                           @"name": [sender name],
                           @"ipv4": ipv4,
                           @"ipv6": ipv6,
                           @"port": [NSNumber numberWithInt:sPort]};
    return data;
}
 
// 解析服务成功
-(void)netServiceDidResolveAddress:(NSNetService *)sender{

    NSLog(@"解析成功 %@",[self parsingIP:sender]);
}

//解析服务失败,解析出错
- (void)netService:(NSNetService *)netService didNotResolve:(NSDictionary *)errorDict {
    
    NSLog(@"didNotResolve: %@",errorDict);
    
}
  1. 遇到的问题:
    按常规理论来说,应该在下面这个函数中直接能获取到设备的ip、名称等信息。
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing;

但是理性很丰满,显示很骨干,在这个函数中获取到的service中,并没有我想要获取到的信息,所以必须给service设置代理,解析之后才能获取到我想要的信息。
所以,实现的方法就成了下面这个样子:

- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {

  service.delegate = self;
  //设置解析超时时间
  [service resolveWithTimeout:5.0];
}

但是,这么写也是不能获取到最终的信息,把人着急成马了。最终百度、谷歌半天,发现写成下面这种方式的话,就可以获取到最后发现设备的信息,算是成功了。。

  - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
        
    self.service = service;
    
    self.service.delegate = self;
    //设置解析超时时间
    [self.service resolveWithTimeout:5.0];
}

ps. 不要问我为什么这么写就可以,我也不知道。

最后,还有一个大坑在等着:如何获取到网络中所有设备的信息呢?有知道的吗?请教一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容

  • 什么是NSD? NSD全称为: Network Service Discovery.也就是网络服务发现的意思。(可...
    JasonYeyeye阅读 7,168评论 1 7
  • 计算机网络基础## 计算机网络是多台独立自主的计算机互联而成的系统的总称,最初建立计算机网络的目的是实现信息传递和...
    jackfrued阅读 4,571评论 6 64
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yueqian...
    BensonLiang阅读 5,747评论 0 4
  • IPv6地址 IPv6地址的表示方法 IPv6地址总长度为128比特,通常分为8组,每组为4个十六进制数的形式,每...
    天楚锐齿阅读 41,313评论 1 20
  • 在网络中,我们经常会用到一些命令检测网络状态或者一些网络相关的信息,以下电脑高手或技术员常用的9大网络命令你都知道...
    whatangle阅读 444评论 0 1