我想实现一个“简单”的 SSDP 发现客户端。意味着客户端应该发送一个
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 1
ST: ssdp:all
然后收听“网络”(?)以获取 IP 地址列表。
为了测试实现,我编写了一个单元测试,它创建了一个“假” MulticastServer
,它只是听到 SSDP IP&Port,当收到一些东西时,发回相同的消息。
问题是这段代码大部分时间都在我的机器(macOS)上工作,但在我们的 CI 服务器(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
它将始终有效。我的实施有问题吗?