数据报 (UDP) 接收器不工作 - 不接收广播数据包

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

我在使用 UDP 数据报时遇到问题,因为我无法从服务器接收 UDP 数据包,但我可以发送它们。我查看了许多示例,但无法弄清楚我的代码有什么问题。我终于从不同的站点找到了问题所在的提示。

因此,我在这里更新了问题,以防将来对某人有所帮助。下面的代码在 LG phone 上通过 WiFi 网络运行,并基于 Android Studio 4.2 (29/4/2021) 构建; SDK 平台 30;科特林 1.5.0

在下面代码部分的末尾,我写了一些关于导致我的代码无法运行的原因的评论。

这是我的 MainActivity 代码

//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"
    }
}

在我的清单文件中我添加了这个权限

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

“activity_new.xml”只包含一个 id 为 MyButton 的按钮和一个 id 为 rcvdData 的 TextView

gradle.build(项目)

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(模块)

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'
}

虽然我可以发送数据报 UDP 数据包,但我无法接收它们。在最终正确接收 UDP 数据包后,我只能接收 UNICAST 消息(消息仅用于我的 IP)而不能接收 BROADCAST 消息(用于多个设备的消息)。

我收不到消息的原因是我在我的电脑上模拟接收器。 Android的模拟器更改了模拟设备的IP,并没有将其绑定到PC的IP地址。这意味着虽然我的 PC 会收到广播消息,但模拟的 phone 却没有。有关详细信息,请查看此 link (https://developer.android.com/studio/run/emulator-networking)

我只能接收 UNICAST 而不能接收 BROADCAST 消息的原因是,一旦我的代码在我的手机上运行 phone,我会在编码时离开 phone。这意味着 phone 的屏幕将进入休眠状态。显然,当 phone 进入睡眠状态以节省电力时,有大量 phone 禁用收听广播消息。一旦 phone 被唤醒,它将收听广播消息。

获得多播锁似乎并没有影响我的 phone 上的这个功能(我只是为了广播而试过这个,所以不幸的是我不知道如果你实际上使用的是多播套接字是否可行数据报套接字)

我找到了解决办法。代码实际上没有问题。我已经更新了原始问题,并评论了导致问题的原因......请参阅上面的问题以获得完整的解释。

简而言之,问题在于 Android 的模拟器具有与 PC IP 不同的 IP,其次 phone 在进入睡眠状态后停止收听广播消息。