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 


@interface FindDeviceService ()

//定义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 *)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. 不要问我为什么这么写就可以,我也不知道。

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

你可能感兴趣的:(iOS开发 Bonjour的使用)