MutlicastSocket получают не всегда - PullRequest
0 голосов
/ 30 апреля 2019

Я хочу реализовать "простой" клиент обнаружения SSDP. Означает, что клиент должен отправить

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 1
ST: ssdp:all

и затем прослушайте «сеть» (?), Чтобы получить список IP-адресов.

Для проверки реализации я написал модульный тест, который создает «фальшивку» MulticastServer, которая просто прослушивает IP-адрес и порт SSDP и, когда что-то получает, отправляет то же сообщение обратно.

Проблема в том, что этот код работает на моей машине (macOS) большую часть времени, но никогда на нашем CI Server (Linux). Я (macOS) получаю иногда ту же ошибку подтверждения ошибки, что и на CI Но как я сказал - только иногда! Не всегда. И я не знаю почему.

Это реализация на стороне клиента:

interface GatewayDiscoverer {

    companion object {
        val instance: GatewayDiscoverer = DefaultGatewayDiscoverer()
    }

    suspend fun discoverGateways(timeoutMillis: Int = 1000): List<String>
}

internal class DefaultGatewayDiscoverer : GatewayDiscoverer {

    override suspend fun discoverGateways(timeoutMillis: Int): List<String> {
        require(timeoutMillis in 1000..5000) {
            "timeoutMillis should be between 1000 (inclusive) and 5000 (inclusive)!"
        }

        val socket = DatagramSocket()
        sendSsdpPacket(socket)

        val gateways = receiveSsdpPacket(socket, timeoutMillis)

        return gateways
    }

    private fun sendSsdpPacket(socket: DatagramSocket) {
        val packetToSend =
            "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 1\r\nST: ssdp:all\r\n\r\n"
        val packetToSendAsBytes = packetToSend.toByteArray()
        val packet = DatagramPacket(
            packetToSendAsBytes,
            packetToSendAsBytes.size,
            InetAddress.getByName("239.255.255.250"),
            1900
        )
        socket.send(packet)
    }

    private fun receiveSsdpPacket(socket: DatagramSocket, timeoutInMillis: Int): List<String> {
        val gatewayList = mutableListOf<String>()

        while (true) {
            val receivedData = ByteArray(12)
            val packetToReceive = DatagramPacket(receivedData, receivedData.size)
            socket.soTimeout = timeoutInMillis
            try {
                socket.receive(packetToReceive)
                packetToReceive.address?.hostName?.let { gatewayList.add(it) }
            } catch (socketTimeout: SocketTimeoutException) {
                return gatewayList
            }
        }
    }
}

И этот тест (включает MulticastServer):

class DefaultGatewayDiscovererTest {

    @Test
    fun `discover gateways should return a list of gateway IPs`() = with(MulticastServer()) {
        start()

        val list = runBlocking { GatewayDiscoverer.instance.discoverGateways(1000) }

        close()
        assertThat(list.size).isEqualTo(1)
        assertThat(list).contains(InetAddress.getLocalHost().hostAddress)
        Unit
    }
}

/**
 * A "MulticastServer" which will join the
 * 239.255.255.250:1900 group to listen on SSDP events.
 * They will report back with the same package
 * it received.
 */
class MulticastServer : Thread(), Closeable {

    private val group = InetAddress.getByName("239.255.255.250")
    private val socket: MulticastSocket = MulticastSocket(1900)

    init {
        // This force to use IPv4...
        var netinterface: NetworkInterface? = null
        // Otherwise it will (at least on macOS) use IPv6 which leads to issues
        // while joining the group...
        val networkInterfaces = NetworkInterface.getNetworkInterfaces()
        while (networkInterfaces.hasMoreElements()) {
            val networkInterface = networkInterfaces.nextElement()
            val addressesFromNetworkInterface = networkInterface.inetAddresses
            while (addressesFromNetworkInterface.hasMoreElements()) {
                val inetAddress = addressesFromNetworkInterface.nextElement()
                if (inetAddress.isSiteLocalAddress
                    && !inetAddress.isAnyLocalAddress
                    && !inetAddress.isLinkLocalAddress
                    && !inetAddress.isLoopbackAddress
                    && !inetAddress.isMulticastAddress
                ) {
                    netinterface = NetworkInterface.getByName(networkInterface.name)
                }
            }
        }

        socket.joinGroup(InetSocketAddress("239.255.255.250", 1900), netinterface!!)
    }

    override fun run() {
        while (true) {
            val buf = ByteArray(256)
            val packet = DatagramPacket(buf, buf.size)
            try {
                socket.receive(packet)
            } catch (socketEx: SocketException) {
                break
            }
            // Print for debugging
            val message = String(packet.data, 0, packet.length)
            println(message)
            socket.send(packet)
        }
    }

    override fun close() = with(socket) {
        leaveGroup(group)
        close()
    }
}

Если тест не пройден, он завершается неудачей в этой строке:

assertThat(list.size).isEqualTo(1)

list пусто.

После некоторой отладки я обнаружил, что MulticastServer не получает сообщение. Поэтому клиент не получает ответ и добавляет IP-адрес к list.

Я бы ожидал, что MulticastServer будет всегда работать без этой "бесполезности". Я что-то не так с реализацией?

...