1

我在尝试让 JmDNS 在 Mac OS X 上运行时遇到问题。症状是我可以发现网络上的任何服务,但我自己的计算机上的服务除外。list他们是在本地主机上还是在我的计算机上运行的虚拟机上都没有关系 - 在任何一种情况下,他们都不会从通话中回来。

我设法将我们正在做的事情浓缩为一个在 Windows 上通过但在 Mac OS X 上失败的测试。现在的问题是我无法弄清楚问题出在哪里。

@Test
public void testAdvertisingOverLoopback() throws Exception
{
    // happens on any address but loopback is the strangest
    InetAddress address = InetAddress.getLoopbackAddress();
    String type = "_test._tcp.local.";
    String name = "test-service";
    int port = 9999;
    Map<String, String> properties = ImmutableMap.of("key", "value");

    // simulate the service starting up. issue also occurs in separate VMs
    try (JmDNS serviceDns = JmDNS.create(address))
    {
        serviceDns.registerService(ServiceInfo.create(type, name, port,
                                   0, 0, properties));

        try (JmDNS clientDns = JmDNS.create(address))
        {
            ServiceInfo[] services = clientDns.list(type);

            // One of the entries should:
            assertThat(services, is(arrayContaining(allOf(

                // Contain an address which matches the one we advertised (culling those which might
                // be registered by other tests which happen to run at the same time.)
                hasProperty("inetAddresses", arrayContaining(sameAddressAs(address))),

                // Match the parameters we specified in the call to list.
                hasProperty("application", equalTo("test")),
                hasProperty("protocol", equalTo("tcp")),
                hasProperty("domain", equalTo("local")),

                // Match the info we advertised.
                hasProperty("port", equalTo(9999)),
                hasCustomProperty("key", "value")
            ))));
        }
    }
}

private static Matcher<InetAddress> sameAddressAs(final InetAddress address)
{
    return new TypeSafeMatcher<InetAddress>()
    {
        @Override
        protected boolean matchesSafely(InetAddress inetAddress)
        {
            return Arrays.equals(address.getAddress(), inetAddress.getAddress());
        }

        @Override
        public void describeTo(Description description)
        {
            description.appendText("same address as ");
            description.appendValue(address.getHostAddress());
        }
    };
}

private static Matcher<ServiceInfo> hasCustomProperty(final String key,
                                                      final String value)
{
    return new TypeSafeMatcher<ServiceInfo>()
    {
        @Override
        protected boolean matchesSafely(ServiceInfo serviceInfo)
        {
            return value.equals(serviceInfo.getPropertyString(key));
        }

        @Override
        public void describeTo(Description description)
        {
            description.appendText("has custom mDNS property ");
            description.appendValue(key);
            description.appendText(" = ");
            description.appendValue(value);
        }
    };
}

在调试器中,我可以看到它没有将套接字绑定到任何特定地址,只是绑定到特定端口。但它随后将其设置为特定界面。

我在 Wireshark 中看到的是数据包来自我机器的公共 IP(en0 的地址),即使 lo0 是我用于测试的接口。我还看到了查询和响应数据包。回应正在回来。

但是在 Java 方面,我看到它在调用DatagramSocket#receive(DatagramPacket)并且从未收到数据包。

(我还花了半天时间寻找 JmDNS 的替代品,但看起来其他声称可以替代它的库实际上还不能进行多播,这让它们有点毫无意义。:()

这里发生了什么?

4

1 回答 1

1

发生的事情是 OS X 中的内置 zeroconf 服务正在获取数据包。

JmDNS 假定它是在侦听该端口的机器上运行的唯一守护进程。因为它使用的代码故意绑定到 0.0.0.0,所以不会抛出关于正在使用的端口的异常(显然这是 Socket API 的“特性”。)

对于 Windows,这很好用,因为没有另一个 zeroconf 守护进程在运行。

对于 Mac OS X,它肯定会失败,因为内置的总是在运行。

我猜在 Linux 上,你会得到好坏参半的结果,这取决于你运行的是哪个发行版以及你安装了哪些服务。

我正在处理的问题的解决方案是在CFNetServices之上创建一个完全不同的 API 。

于 2014-03-12T02:33:08.860 回答