Search code examples
androidkotlinudpktor

Can't receive UDP datagrams using Ktor raw socket on Android


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()} ---")
}

Solution

  • 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?.