Я хочу реализовать "простой" клиент обнаружения 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
будет всегда работать без этой "бесполезности". Я что-то не так с реализацией?