在电视的实际应用中,投屏功能占了很大的一个比重,熟悉和掌握投屏的原理和应用,对于
我们开发和测试人员是必不可少的;在投屏时,首先第一步,手机和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 服务的注册和设备发现
import java.io.IOException;
import java.net.InetAddress;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
public class ExampleServiceRegistration {
public static void main(String[] args) throws InterruptedException {
try {
// Create a JmDNS instance
JmDNS jmdns = JmDNS.create(InetAddress.getLocalHost());
// Register a service
ServiceInfo serviceInfo = ServiceInfo.create("_http._tcp.local.", "example", 1234, "path=index.html");
jmdns.registerService(serviceInfo);
// Wait a bit
Thread.sleep(25000);
// Unregister all services
jmdns.unregisterAllServices();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
public class ExampleServiceDiscovery {
private static class SampleListener implements ServiceListener {
@Override
public void serviceAdded(ServiceEvent event) {
System.out.println("Service added: " + event.getInfo());
}
@Override
public void serviceRemoved(ServiceEvent event) {
System.out.println("Service removed: " + event.getInfo());
}
@Override
public void serviceResolved(ServiceEvent event) {
System.out.println("Service resolved: " + event.getInfo());
}
}
public static void main(String[] args) throws InterruptedException {
try {
// Create a JmDNS instance
JmDNS jmdns = JmDNS.create(InetAddress.getLocalHost());
// Add a service listener
jmdns.addServiceListener("_http._tcp.local.", new SampleListener());
// Wait a bit
Thread.sleep(30000);
} catch (UnknownHostException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
上面这段代码在是一个简单的设备注册示例;主要分为两部分
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 = ServiceInfo.create(raopName + raopType, raopName,
RAOP_PORT, 0, 0, values);
jmdnsRaop = JmDNS.create(inetAddress);
jmdnsRaop.registerService(raopService);
}
private void startAirplay(String devicename) {
………
airplayService = ServiceInfo.create(devicename + airplayType, devicename,
AIRPLAY_PORT, 0, 0, values);
jmdnsAirplay = JmDNS.create(inetAddress);
jmdnsAirplay.registerService(airplayService);
}
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;
if (info.getDns() != null) {
if (info.getDns() != this) {
throw new IllegalStateException("A service information can only be registered with a single instamce of JmDNS.");
} else if (_services.get(info.getKey()) != null) {
throw new IllegalStateException("A service information can only be registered once.");
}
}
info.setDns(this);
this.registerServiceType(info.getTypeWithSubtype());
// bind the service to this address
info.recoverState();
info.setServer(_localHost.getName());
info.addAddress(_localHost.getInet4Address());
info.addAddress(_localHost.getInet6Address());
this.makeServiceNameUnique(info);
while (_services.putIfAbsent(info.getKey(), info) != null) {
this.makeServiceNameUnique(info);
}
this.startProber();
logger.debug("registerService() JmDNS registered service as {}", info);
}
有兴趣的朋友可以点击进入项目,下载源码
项目地址