设备发现[Airplay投屏应⽤]-mdns协议简介背景
在电视的实际应⽤中,投屏功能占了很⼤的⼀个⽐重,熟悉和掌握投屏的原理和应⽤,对于
我们开发和测试⼈员是必不可少的;在投屏时,⾸先第⼀步,⼿机和IPAD等客户端需要发
现局域⽹中的⽬标投屏设备(也即电视),才能将⾳视频或图⽚或镜像投放到电视上去。
在局域⽹中,设备和设备之前相互通信需要知道对⽅的ip地址的,⼤多数情况,设备的ip
不是静态ip地址,⽽是通过dhcp 协议动态分配的ip 地址,如何设备发现呢,就是要mdns
⼤显⾝⼿,例如:现在物联⽹设备和app之间的通信,要么app通过⼴播,要么通过组播,
发⼀些特定信息,感兴趣设备应答,实现局域⽹设备的发现,当然mdns ⽐这强⼤的多。
简介
Mdns 即多播dns(Multicast DNS),mDNS主要实现了在没有传统DNS服务器的情况下使
局域⽹内的主机实现相互发现和通信,使⽤的端⼝为5353,遵从dns协议,使⽤现有的DNS
信息结构、名语法和资源记录类型。并且没有指定新的操作代码或响应代码。
本⽂简要的介绍⼀下Mdns的⼯作原理,设备注册和设备发现。
⼯作原理
Mdns 使⽤组播地址为: 224.0.0.251 (ipv6:FF02::FB) 端⼝为5353,mdns 是⽤于局域
⽹内部的,并且主机的域名为.local 结尾,每个进⼊局域⽹的主机,如果开启了mDNS服务的
话,都会向局域⽹内的所有主机组播⼀个消息,我是谁(域名),和我的IP地址是多少。然
后其他有Mdns服务的主机就会响应,也会告诉你,它是谁(域名),它的IP地址是多少。
当然设备需要服务时,就是使⽤Mdns查询域名对应的ip地址,对应的设备收到该报⽂后
同样通过组播⽅式应答,此时其他主机设备也是可以收到该应答报⽂,其他主机也会记录域
名和ip 以及ttl 等,更新缓存。
简单的来说,同在⼀个局域⽹的投屏设备,源设备和⽬标设备会加⼊同⼀个组播组【IP:
224.0.0.251 PORT: 5353 】;
⽬标设备,主要是指电视或电脑等⼤屏设备,在服务启动时,会根据设备名和MAC注册两
个服务(_raop._tcp.local和_airplay._tcp.local),并向组播地址(224.0.0.251)发布⼴播,宣称⾃
⼰提供的服务;
源设备,也即苹果⼿机或Ipad等,在打开视频APP 投屏时(⼀般都会有个类似TV图标提⽰ ),
或在浏览相册需要进⾏投屏时,向组播地址询问是否有_raop._tcp.local和_airplay._tcp.local
服务的设备,组内其他的主机在接收到请求后,如果本机注册了该两项服务,则会就⾏应对,
应答时,包含了本机的IP 地址和所提供的服务。
⼤概的原理就是这样⼦,mDNS提供的服务要远远多于这个,当然服务多但并不复杂。
Airplay协议⽤Bonjour做设备发现,它是Apple公司为基于组播域名服务(multicast DNS)的开
放性零配置⽹络标准所起的名字。
以下我们以开源项⽬Jmdns 为例讲解mDNS 服务的注册和设备发现
Sample Code for Service Registration
import JmDNS;
import ServiceInfo;
public class ExampleServiceRegistration {
public static void main(String[] args)throws InterruptedException {
try{
// Create a JmDNS instance
JmDNS jmdns = LocalHost());
// Register a service
ServiceInfo serviceInfo = ate("_http._tcp.local.","example",1234,"path=index.html");            isterService(serviceInfo);
// Wait a bit
Thread.sleep(25000);
// Unregister all services
jmdns.unregisterAllServices();
}catch(IOException e){
System.out.Message());
}
}
}
Sample code for Service Discovery
import UnknownHostException;
import JmDNS;
import ServiceEvent;
import ServiceListener;
public class ExampleServiceDiscovery {
private static class SampleListener implements ServiceListener {
@Override
public void serviceAdded(ServiceEvent event){
System.out.println("Service added: "+ Info());
}
@Override
public void serviceRemoved(ServiceEvent event){
System.out.println("Service removed: "+ Info());
}
@Override
public void serviceResolved(ServiceEvent event){
System.out.println("Service resolved: "+ Info());
}
}
public static void main(String[] args)throws InterruptedException {
try{
// Create a JmDNS instance
JmDNS jmdns = LocalHost());
/
/ Add a service listener
jmdns.addServiceListener("_http._tcp.local.",new SampleListener());
// Wait a bit
Thread.sleep(30000);
}catch(UnknownHostException e){
System.out.Message());
}catch(IOException e){
System.out.Message());
}
}
}
上⾯这段代码在是⼀个简单的设备注册⽰例;主要分为两部分
1. 创建⼀个ServiceInfo 实例
2. 将刚刚创建的ServerInfo进⾏注册,并进⾏⼴播
PART1: ServiceInfo 详细
ServiceInfo create(final String type,final String name,final int port,final String text)
@param type服务类型,Bonjour要求格式为"_服务名._传输协议",例如"_ftp._tcp";⽬前传输协议仅⽀持TCP和UDP @param name[唯⼀的]服务名称
@param port服务端⼝号,如果为0的话,Bonjour会⾃动分配⼀个
@param text对服务的描述
以Bonjour 为例,Airplay 投屏需要创建两个服务,分别是_raop._tcp.local和_airplay._tcp.local
private void startRaop(String devicename){
………
String raopName = preMac +"@"+ devicename;
raopService = ate(raopName + raopType, raopName,
RAOP_PORT,0,0, values);
jmdnsRaop = ate(inetAddress);
}
private void startAirplay(String devicename){
………
airplayService = ate(devicename + airplayType, devicename,
AIRPLAY_PORT,0,0, values);
jmdnsAirplay = ate(inetAddress);
}
Devicename表⽰本机的设备名
raopType="._raop._tcp.local";
airplayType="._airplay._tcp.local"
raopService的raopName中需要加⼊设备的MAC地址后⾯紧跟⼀个@,否则服务将不被识别。
PART2: 服务注册和⼴播
javax.jmdns.impl.JmDNSImpl.java
public void registerService(ServiceInfo infoAbstract)throws IOException {
if(this.isClosing()||this.isClosed()){
throw new IllegalStateException("This DNS is closed.");
}
final ServiceInfoImpl info =(ServiceInfoImpl) infoAbstract;
Dns()!=null){
Dns()!=this){
throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS.");
}else if(_(Key())!=null){
throw new IllegalStateException("A service information can only be registered once.");
}
}
info.setDns(this);
// bind the service to this address
info.setServer(_Name());
param nameinfo.addAddress(_Inet4Address());
info.addAddress(_Inet6Address());
this.makeServiceNameUnique(info);
while(_services.Key(), info)!=null){
this.makeServiceNameUnique(info);
}
this.startProber();
logger.debug("registerService() JmDNS registered service as {}", info);
}
有兴趣的朋友可以点击进⼊项⽬,下载源码