zoukankan      html  css  js  c++  java
  • mDNS之airplay实现及问题总结

            mDNS实现之mdnsresponder介绍

     

    一、名词介绍

    mdnsresponder是Apple实现Benjour的一个开源工程。

    BonjourApple基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。Bonjour技术在Mac OS以及iTunes、iPhone上广泛应用(airplay)

    zeroconf(Zero configuration networking)零配置网络服务规范,是一种用于自动生成可用IP地址的网络技术,不需要额外的手动配置和专属的配置服务器。Zeroconf规范的提出者是Apple公司。

    mDNS:即组播域名服务(multicast DNS)。使用5353端口,在内网没有DNS服务器时,就会出现此组播信息。mNDSS是实现跟DNS相似服务,使得在没有NDS服务的情况下使局域网内的主机实现相互发现和通信。(The name "mDNS" was chosen because this protocol is designed to be,as much as possible, similar to conventional DNS)

     

    二、实现机制

    开源工程mDNSResponder实现了 Bonjour协议的服务名称与地址的转换以及服务的发现等 Bonjour部分协议的支持。Bonjour协议的服务名称与地址的转换以及服务的发现采用的流程和DNS流程近似包括:登记过程、服务发现过程、服务 地址解析过程以及建立连接等过程,服务发现采用的协议也和DNS协议相似,不过与DNS协议采用的单播方式不同的是采用了组播方式,因此被称为mDNS。

    mdnsresponder是C代码实现,支持多种平台,在Windows平台上,它将生成一个后台程序mdnsresponder。在Android平台上(或者说支持POSIX的Linux平台)它是一个名为mdnsd的程序。不过,不论是mdnsresponder还是mdnsd,应用开发者要做的仅仅是利用工程中提供的API向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。mdnsd或者mdnsresponder作为守护进程,在开机启动时就开启,用户通过调用dns_sd.h里的API接口来实现服务注册、服务查询和服务解析等功能

    三、主要的API接口

    服务注册的API为DNSServiceRegister,原型如下。
    DNSServiceErrorType DNSSD_API DNSServiceRegister
    (
        DNSServiceRef                        *sdRef,
        DNSServiceFlags                flags,
        uint32_t                        interfaceIndex,
        const char                        *name,                /* may be NULL */
        const char                        *regtype,
        const char                        *domain,        /* may be NULL */
        const char                        *host,                /* may be NULL */
        uint16_t                        port,                /* In network byte order */
        uint16_t                        txtLen,
        const void                        *txtRecord,        /* may be NULL */
        DNSServiceRegisterReply        callBack,        /* may be NULL */
        void                                *context        /* may be NULL */
    );
    该函数的解释如下。
    sdRef代表一个未初始化的DNSService实体,其类型DNSServiceRef是指针。该参数最终由DNSServiceRegister函数分配内存并初始化。
    flags表示当网络内部有重名服务时的冲突处理。默认是按顺序修改服务名。例如要注册的服务名为“printer”,当检测到重名冲突时,就可改名为“printer(1)”。
    interfaceIndex表示该服务输出到主机的哪些网络接口上。值-1表示仅对本机支持,也就是该服务的用在loop接口上。
    name表示服务名,如果为空就取机器名。
    regtype表示服务类型,用字符串表达。Bonjour要求格式为“_服务名._传输协议”,例如“_ftp._tcp”。目前传输协议仅支持TCP和UDP。
    domian和host一般都为空。
    port表示该服务的端口。如果为0,Bonjour会自动分配一个。
    txtLen以及txtRecord字符串用来描述该服务。
    txtRecord格式为键值对(name/value pairs)例如:0x0A | name=value | 0x08 | paper=A4 | 0x12 | Rendezvous Is Cool |
    callBack表示设置回调函数。该服务注册的请求结果都会通过它回调给客户端。
    context表示上下文指针,由应用程序设置。
    当客户端需要搜索网络内部特定服务时,需要使用DNSServiceBrowser API,其原型如下。
    DNSServiceErrorType DNSSD_API DNSServiceBrowse
    (
        DNSServiceRef                        *sdRef,
        DNSServiceFlags                flags,
        uint32_t                        interfaceIndex,
        const char                        *regtype,
        const char                        *domain,        /* may be NULL */
        DNSServiceBrowseReply                callBack,
        void                                *context        /* may be NULL */
    );
    其中,sdref、interfaceIndex、regtype、domain以及context含义与DNSServiceRegister一样。flags在本函数中没有作用。callBack为DNSServiceBrowser处理结果的回调通知接口。
    当客户端想获得指定服务的IP和端口号时,需要使用DNSServiceResolve API,其原型如下。
    DNSServiceErrorType DNSSD_API DNSServiceResolve
    (
        DNSServiceRef                        *sdRef,
        DNSServiceFlags                flags,
        uint32_t                        interfaceIndex,
        const char                        *name,
        const char                        *regtype,
        const char                        *domain,
        DNSServiceResolveReply        callBack,
        void                                *context        /* may be NULL */
    );
    其中,name、regtype和domain都从DNSServiceBrowse函数的处理结果中获得。callBack用于通知DNSServiceResolve的处理结果。该回调函数将返回服务的IP地址和端口号

    mdnsresponder在linux上的实现

    一、        工程源码

    http://www.opensource.apple.com/source/mDNSResponder/只能浏览,没有提供下载。

    http://www.opensource.apple.com/tarballs/mDNSResponder/ 各个版本的打包文件,直接下载。

    本文以选用mDNSResponder-320.10.80。

    二、工程目录介绍

     

    mDNSCore:主要核心协议引擎代码,纯C语言编写,各个平台都需要依赖该核心代码。

    mDNSShared:多个平台共享的非核心引擎代码。

    mDNSPosix:Posix平台相关代码。

    Clients:包括如何使用后台服务提供的API的客户端例子代码等四个目录。

    在linux下实现只需要以上几个目录代码。

    使用mDNSPosix的Makefile编译(make os=linux)生成以下文件(/build/prod),可以修改Makefile的Debug=1项来生成有debug信息的文件(/build/debug下)

     

    编译Clients生成一个dns-sd执行文件用于测试,用于跟mndsd服务通讯。

    其中mdnsd是一个后台服务,这个服务应该设置随着系统启动时运行,libmdnssd是一个 MDns监视层(dns-sd)使用的库libmdnssd。

     

     

    专用设备使用文件 (printer, network camera, etc.)

      - mDNSClientPosix

      - mDNSResponderPosix

      - mDNSProxyResponderPosix

     

    要把程序运行在嵌入式系统板上,需要修改Makefile来进行交叉编译

    ifeq ($(findstring linux,$(os)),linux)

    CFLAGS_OS = -D_GNU_SOURCE -DHAVE_IPV6 -DNOT_HAVE_SA_LEN -DUSES_NETLINK -DHAVE_LINUX -DTARGET_OS_LINUX -fno-strict-aliasing

    LD = gcc –shared

    改为

    CC = /opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc

    LD = /opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc –shared

     

    /opt/mtk/gnu-toolchain_4.8.2_2.6.35_cortex-a9-neon/bin/armv7a-mediatek482_001_neon-linux-gnueabi-gcc是具体平台的编译工具链

     

    生成mdnsd放到板子上运行,将生成的dns-sd运行起来,./dns-sd –h可以看到dns-sd测试程序的测试提示信息。接下来可以修改dns-sd.c里的代码来定制自己的测试项。

    下面是一个注册airplay服务和raop服务的demo:

     

    • Demo:  
    •     {  
    •   
    • #define kRaopPort   50001  
    • #define kAirplayPort    50002  
    •   
    •         static DNSServiceRef airplayRef    = NULL;  
    •         static DNSServiceRef raopRef    = NULL;  
    •   
    •         Opaque16 AirplayPort = { { kAirplayPort >> 8, kAirplayPort & 0xFF } };  
    •         Opaque16 RaopPort = { { kRaopPort >> 8, kRaopPort & 0xFF } };  
    •   
    •         static const char AirplayTXT[] =   
    •             "\x1A" "deviceid=0c:54:a5:56:9d:80" \  
    •             "\x0F" "features=0x3FFF"; \  
    •             //"\x10" "model=AppleTV3,1";  
    •             //"\x0E" "srcvers=150.33";  
    •   
    •         static const char RaopTXT[] =   
    •             "\x06" "tp=UDP" \  
    •             "\x08" "sm=false" \  
    •             "\x08" "sv=false" \  
    •             "\x04" "ek=1" \  
    •             "\x06" "et=0,1" \  
    •             "\x06" "cn=0,1" \  
    •             "\x04" "ch=2" \  
    •             "\x05" "ss=16" \  
    •             "\x08" "sr=44100" \  
    •             "\x08" "pw=false" \  
    •             "\x04" "vn=3" \  
    •             "\x09" "txtvers=1";  
    •               
    •   
    •         err = DNSServiceRegister(&airplayRef, 0, opinterface, "JieTools", "_airplay._tcp.", "", NULL, AirplayPort.NotAnInteger, 0, NULL, reg_reply, NULL);  
    •         if (!err) err = DNSServiceUpdateRecord(airplayRef, NULL, 0, sizeof(AirplayTXT)-1, AirplayTXT, 0);  
    •   
    •         err = DNSServiceRegister(&raopRef, 0, opinterface, "0C54A5569D80@JieTools", "_raop._tcp.", "", NULL, RaopPort.NotAnInteger, 0, NULL, reg_reply, NULL);  
    •         if (!err) err = DNSServiceUpdateRecord(raopRef, NULL, 0, sizeof(RaopTXT)-1, RaopTXT, 0);  
    •   
    •         while(1)getchar();  
    •           
    •         return 0;  
    •     }  
    • #endif

     

    前两行定义指定服务端口,而后的AirplayTXT与RaopTXT分别两个服务的描述内容,下面对AirplayTXT做简单说明:

    "\x1A"这样的写法,是为字符串前添加长度字值,为16进制,deviceid后面的值是本机网卡的物理地址,features这个参数不能少,它是airplay服务所支持的特性或能力描述,其它的参数可以忽略。

    RaopTXT描述内容是我通过抓包COPY下来的,没有修改过;再接下来调用了两个mDNS SDK中的两个API,DNSServiceRegister用于注册,DNSServiceUpdateRecord用来更新服务的TXTRecord信息。

    这里有两组调用服务注册,这里需要注意的是,如果你想实现_airplay服务,那么就必须将这两个服务一起注册,并且服务名称必须一致,如第四个参数是服务名称“JieTools”及“0C54A5569D80@JieTools”,注意命名规则。

    OK,不出意外的话,运行它,打开你的手机,就能在airplay中发现自己注册的这个服务了

                                                          问题总结

    1、  选择合适测试平台

    测试应该选择合适平台,由于我的目的是要移植到linux arm平台,所以我选择linux环境来编译测试,本机在虚拟机上安装ubuntu,在ubuntu上进行编译测试,编译运行都没有问题,但是手机端怎么都发现不了设备,使用工程里ReadMe的测试方法:mDNSResponderPosix mDNSClientPosix测试也没起作用,最后是重新编译放到板子上运行,手机端能够发现到所注册的服务了。测试平台不能选择虚拟机
    2、在测试过程中怎么看打印信息
    默认情况下,打印信息都保存到/var/log/system.log里面,在运行mdnsd是带上debug参数(mdnsd -debug),或者把Makefile的编译项改成DEBUG=1就能在窗口上实视看到打印输出。调试过程中建议把debug信息打印出来,方便跟踪问题。

    3、  调用函数DNSServiceRegister会返回-65549

    返回-65549是错误代码,这种情况一版是参数不对造成,一般情况下,txtRecord参数容易出错,debug信息提示

    Sep 15 16:06:13 localhost mDNSResponder[192]: Attempt to register record with invalid rdata: 17 Ice Cube._http._tcp.local. TXT ath=/index.html

    TXT record的格式是:长度键值对长度键值对(length byte, data, length byte, data)

    长度是16进制表示,键值对是=左边是字符串,右边是值,各个键值对之间没有间隔如:\011txtvers=1\020path=/index.html\025note=Bonjour Is Cool!

    4、  要在ios端的airplay上发现服务,需要一些专有参数

    对DNSServiceRegister函数的参数,regtype参数必须是_服务名._传输协议,并且只支持tcp和udp,其他的参数都没有做特殊要求。但是如果要让ios能够发现服务,_airplay._tcp 和_raop._tcp 两种服务都要注册,并且两个服务名name要一样,如:airplay的服务名称是hzzTools ,则raop的服务名称是0C54A5569D80@JieTools,其中0C54A5569D80是MAC地址。参数txtRecord里两个参数是必须的,deviceid本机网卡的物理地址和features

    5、  注册服务后还需要更新服务信息

    调用DNSServiceRegister注册服务后,还需要调用DNSServiceUpdateRecord来更新服务的TXTRecord信息。这样ios端才能发现到服务。

    6、  在测试过程中除了打开debug信号来跟踪,还可以用抓包工具(如:Wireshark)来分析

    通过使用抓包工具来分析,可以最直接的分析到设备间的网络通讯情况。熟练使用工具能够跟快跟踪到问题所在。

  • 相关阅读:
    notepad++的使用
    windows下的ubuntu
    VMware Tools安装
    Terminal命令
    Linux文件操作
    vim学习
    Windows桌面美化
    求解移动字符串问题
    求解回文序列问题
    用Git命令把本地项目,提交到远程仓库
  • 原文地址:https://www.cnblogs.com/seven-sky/p/4729962.html
Copyright © 2011-2022 走看看