I'm trying to receive a UDP datagram using Ktor and coroutines. I've created a simple android kotlin app and managed to receive a datagram using blocking java.nio.DatagramChannel, but not with Ktor's sockets. Similar suspending approach works in a desktop kotlin app. The app was tested on the Android Studio Emulator (API 30 device) and on an Android 11 phone.
Android app
Added to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Added to dependencies in build.gradle:
implementation 'io.ktor:ktor-network:2.2.4'
MainViewModel.kt:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel
import java.util.concurrent.Executors
class MainViewModel: ViewModel() {
private val receiverService = Executors.newSingleThreadExecutor()
private var messageChannel = DatagramChannel.open()
private val selector = SelectorManager(Dispatchers.IO)
private val portBlocking = 15100
private val portSuspend = 15101
init {
addCloseable(selector)
addCloseable(messageChannel)
receiveBlocking()
receiveSuspend()
}
private fun receiveBlocking() {
val bindAddress = java.net.InetSocketAddress(portBlocking)
messageChannel.bind(bindAddress)
println("--- Blocking receive at ${messageChannel.localAddress} UDP ---")
receiverService.submit {
val buf = ByteBuffer.allocate(1000)
messageChannel.receive(buf)
println("--- Received blocking $buf ---")
}
}
private fun receiveSuspend() = viewModelScope.launch {
val localAddress = InetSocketAddress("127.0.0.1", portSuspend)
val inSocket = aSocket(selector).udp().bind(localAddress) { reuseAddress = true }
println("--- Suspend receive at ${inSocket.localAddress} UDP ---")
val datagram = inSocket.receive()
println("--- Received suspend ${datagram.packet.readText()} ---")
}
}
Working desktop app
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import kotlinx.coroutines.*
const val port = 9000
fun main(args: Array<String>) = runBlocking {
val selectorManager = SelectorManager(Dispatchers.IO)
receiveSuspend(selectorManager)
}
private suspend fun receiveSuspend(selector: SelectorManager) {
val localAddress = InetSocketAddress("127.0.0.1", port)
val inSocket = aSocket(selector).udp().bind(localAddress) { reuseAddress = true }
println("--- Suspend receive at ${inSocket.localAddress} UDP ---")
val datagram = inSocket.receive()
println("--- Received ${datagram.packet.readText()} ---")
}
I encountered the same problem. You actually need to specify the IP Address of the sender. If you don't know, you can simply use "0.0.0.0" (broadcast receive).
val inSocket = aSocket(selector).udp().bind(InetSocketAddress("0.0.0.0", port))
See How to bind to the correct address and port for UDP communication?.