mDNS浅析及应用

本文出自:【InTheWorld的博客】

mDNS是multicast DNS,mDns实现了在局域网内的服务发现功能。mDNS协议应用非常广泛,其中苹果的Bonjour就是mDNS的一个实现,此外Android 4.1之后也加入了对mDNS协议的支持。那么不禁要问,mDNS是如何实现局域网内的服务发现功能的呢?其实答案就在问题中,multicast + DNS。

mDNS协议基于多播网络技术,它工作于UDP的5353端口。mDNS选择多播作为其实现方式,是有一定原因的。首先 ,单播网络不适合用于局域网络的服务 ,因为单播是点对点的,而服务的信息却应该面向多个客户的。其次,广播也是有局限的,为了避免广播风暴,广播是不能穿越子网的。所以mDNS选择多播技术的主要原因是多播面向多客户端、以及子网穿越的特点。多播节省带宽的特性在局域网内其实意义不大。

  • JmDNS的使用与实现

DNS的内容,我目前还掌握的不多,各种记录的含义并没有仔细了解。所以这里对DNS部分就不做过多的分析了,简单地认为它记录了一些服务的地址以及端口信息。mDNS其实是一个纯粹的应用层协议,也存在多种实现。其中,JmDNS就是mDNS在Java上的实现。下面的代码是一个简单的JmDNS示例:

public class DiscoverServices {
    static class SampleListener implements ServiceListener {
        public void serviceAdded(ServiceEvent event) {
            System.out.println("Service added   : " + event.getName()+"."+event.getType());
        }
        public void serviceRemoved(ServiceEvent event) {
            System.out.println("Service removed : " + event.getName()+"."+event.getType());
        }
        public void serviceResolved(ServiceEvent event) {
            System.out.println("Service resolved: " + event.getInfo());
        }
    }
    public static void main(String[] args) {
        try {
            JmDNS jmdns = JmDNS.create();
            jmdns.addServiceListener("_http._tcp.local.", new SampleListener());           
            int b;
            while ((b = System.in.read()) != -1 && (char) b != 'q'); 
            jmdns.close();
            System.out.println("Done");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用JmDNS的方式非常简单,基本上就是填充这几个回调函数。在回调中处理相应的事务逻辑即可,比如在serviceResolved中实现服务连接逻辑等等。

核心的事情都是JmDNS在做,其实JmDNS的代码量不大的,这里分析几处代码,算是揭一揭JmDNS的面纱吧!JmDNS的功能基本都是由JmDNSImpl实现的,JmDNSImpl类有几个关键的函数,分别是初始化函数init()和启动函数start()。其中init方法的代码如下:

    private void init(InetAddress address, String name) throws IOException
    {
        localHost = new HostInfo(address, name);
        cache = new DNSCache(100);
        listeners = Collections.synchronizedList(new ArrayList());
        serviceListeners = new HashMap();
        typeListeners = new ArrayList();

        services = new Hashtable(20);
        serviceTypes = new Hashtable(20);

        timer = new Timer();
        new RecordReaper(this).start(timer);
        shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown");
        Runtime.getRuntime().addShutdownHook(shutdown);

        incomingListener = new Thread(new SocketListener(this), "JmDNS.SocketListener");

        // Bind to multicast socket
        openMulticastSocket(getLocalHost());
        start(getServices().values());
    }

init方法主要实现了各种服务监听相关的数据结构,然后创建了一个线程incomingListener。顾名思义,这个线程就是用来接收和处理进来的网络数据。然后在函数的倒数两行,实现了多播地址的绑定以及mDNS服务的启动。这个start函数的功能相比之下显得更简单了,代码如下:

    private void start(Collection serviceInfos) {
        setState(DNSState.PROBING_1);
        incomingListener.start();
        new Prober(this).start(timer);
        for (final Iterator iterator = serviceInfos.iterator(); iterator.hasNext();) {
            try {
                registerService(new ServiceInfoImpl((ServiceInfoImpl) iterator.next()));
            } catch (final Exception exception) {
                logger.log(Level.WARNING, "start() Registration exception ", exception);
            }
        }
    }

没有骗人吧!真的很简单,基本就是各个线程的启动。比如说incomingListener服务进程,以及Prober这个探测进程,它们工作的方式基本都是事件驱动型的,一直等待着相应事件的发生。这样简单地分析一下可以发现,mDNS其实并不是一个特别复杂的系统。当然这里我并没有分析它的DNS部分的逻辑,因为现在还不熟悉。个人打算在随后分析一下DNS部分的代码,权当学习DNS系统了,我估计这个比BIND应该要简单不少。

此外,Android系统下的Nsd Service也是相当类似了,毕竟它其实就是mDNS在Android下的实现。所以也就不对它的使用方式再做解析了,实现方式倒是可以去分析一下,以后再说了!

发表评论