androidnetwork-programmingudpbroadcastdatagram

Datagram (UDP) receiver not working - not receiving broadcast packets


I had a problem with UDP Datagrams in that I could not receive UDP packets from a server but I could send them. I looked through many examples but could not figure out what was wrong with my code. I finally found hints to what was going wrong from different sites.

I have thus updated the question here in case it might help someone in the future. The code below is working over a WiFi network on a LG phone and was built on Android Studio 4.2 (29/4/2021); SDK Platform 30; Kotlin 1.5.0

At the end of the code section below I have written some comments as to what was causing my code not to work.

This is my MainActivity code

//Required includes
import android.os.Bundle
import android.os.StrictMode
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.io.IOException
import java.net.*


class MainActivity : AppCompatActivity() {

    //declared variables
    private var clientThread: ClientThread? = null
    private var thread: Thread? = null
    private var tv:TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new)

        //Create a thread so that the received data does not run within the main user interface
        clientThread = ClientThread()
        thread = Thread(clientThread)
        thread!!.start()

        // create a value that is linked to a button called (id) MyButton in the layout
        val buttonPress = findViewById<Button>(R.id.MyButton)
        tv = findViewById(R.id.rcvdData)
        tv!!.text = "Data Captured"

        //Create a listener that will respond if MyButton is clicked
        buttonPress.setOnClickListener{
            //send a UDP package as a test
            sendUDP("Hello")
        }
    }



    //************************************ Some test code to send a UDP package
    fun sendUDP(messageStr: String) {
        // Hack Prevent crash (sending should be done using a separate thread)
        val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
        StrictMode.setThreadPolicy(policy)  //Just for testing relax the rules...
        try {
            //Open a port to send a UDP package
            val socket = DatagramSocket()
            socket.broadcast = true
            val sendData = messageStr.toByteArray()
            val sendPacket = DatagramPacket(sendData, sendData.size, InetAddress.getByName(SERVER_IP), SERVERPORT)
            socket.send(sendPacket)
            println("Packet Sent")
        } catch (e: IOException) {
            println(">>>>>>>>>>>>> IOException  "+e.message)
        }
    }

    //************************************* Some test code for receiving a UDP package
    internal inner class ClientThread : Runnable {
        private var socket: DatagramSocket? = null
        private val recvBuf = ByteArray(1500)
        private val packet = DatagramPacket(recvBuf, recvBuf.size)
        // **********************************************************************************************
        // * Open the network socket connection and start receiving a Byte Array                        *
        // **********************************************************************************************
        override fun run() {

            try {
                //Keep a socket open to listen to all the UDP trafic that is destined for this port
                socket = DatagramSocket(CLIENTPORT)
                while (true) {
                    //Receive a packet
                    socket!!.receive(packet)  

                    //Packet received
                    println("Packet received from: " + packet.address.hostAddress)
                    val data = String(packet.data).trim { it <= ' ' }
                    println("Packet received; data: $data")
                    //Change the text on the main activity view
                    runOnUiThread { tv?.text = data }
                }
            }
            catch (e1: IOException) {
                println(">>>>>>>>>>>>> IOException  "+e1.message)
                socket?.close()
            }
            catch (e2: UnknownHostException) {
                println(">>>>>>>>>>>>> UnknownHostException  "+e2.message)
                socket?.close()
            }
            finally{
                socket?.close()
            }
        }
    }

    companion object {
        val CLIENTPORT = 3000
        val SERVERPORT = 3000
        val SERVER_IP = "192.168.8.102"
    }
}

In my manifest file I added this permissions

<uses-permission android:name="android.permission.INTERNET"/>

The "activity_new.xml" only contains a button with id MyButton and a TextView with the id rcvdData

gradle.build (Project)

buildscript {
    ext.kotlin_version = "1.5.0"
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

gradle.build (Module)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.udptry1"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

Although I could send datagram UDP packets I could not receive them. After finally getting it right to receive UDP packets, I could only receive UNICAST messages (messages only intended for my IP) and not BROADCAST messages (messages intended for multiple devices).

The reason I could not receive messages was that I was emulating the receiver on my PC. Android's emulator changes the IP of the emulated device and does not bind it to the PC's IP address. This meant that although my PC would receive broadcasted messages, the emulated phone did not. For more details look at this link (https://developer.android.com/studio/run/emulator-networking)

The reason I could only receive UNICAST and not BROADCAST messages was that once I got the code working on my cellphone, I would leave the phone while coding. This meant that the phone's screen would go to sleep. Apparently there is a large number of phones that DISABLE LISTENING to broadcast messages when the phone goes to sleep to conserve electricity. Once the phone is awake it would listen to broadcast messages.

Getting a multicast lock did not seem to affect this functionality on my phone (I tried this only for broadcasting, so unfortunately I don't know if it might work if you were actually using a multicastsocket instead of a datagramsocket)


Solution

  • I have found the solution. There was actually no issue with the code. I have updated the original question with comments on what caused the problem... Please see the question above for a full explanation.

    In short the problem was with Android's emulator having a different IP to the PC IP and secondly the phone stopped listening to broadcasted messages once it goes to sleep.