You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android App向ESP32发送UDP数据包失败问题求助

ESP32 AP模式下Android App无法发送UDP应答数据包问题

整体架构

  • ESP32配置为Access Point模式
  • ESP32通过UDP广播定期发送连接请求
  • Android App连接ESP32的WiFi(硬编码凭据)
  • App接收ESP32的广播请求并发送应答,完成双向互认

问题描述

App能正常接收ESP32的UDP广播包,但无法发送UDP应答数据包。用笔记本连接该WiFi并通过WireShark抓包,能看到ESP32的广播消息,但完全看不到App发出的UDP流量。

已确认ESP32功能正常:用Python从笔记本发送UDP数据包,ESP32能正常接收并建立后续TCP连接。

曾出现一次短暂正常情况:断开手机与电脑的连接并手动登录WiFi热点时,App能发送UDP应答,但之后无法复现。

更新:发现Android可以向广播地址(255.255.255.255)或自身IP发送UDP包,但无法发送到其他IP,怀疑是Android内置防火墙限制。


ESP32相关代码

热点与UDP广播代码

const IPAddress gateWay(192, 168, 1, 1);
const IPAddress localIP(192, 168, 1, 1);
const IPAddress subnet(0, 0, 0, 0);

void setup(){
  Serial.begin(115200);
  // sdUtils.begin();
  imu.begin(FILTER_UPDATE_RATE_HZ);

  // configure esp as access point
  WiFi.softAP(SSID, PASS);
  //WiFi.config(localIP, gateWay, subnet);
  WiFi.softAPConfig(localIP, gateWay, subnet);
  Serial.println(udp.begin(2255));
}

void loop(){
  // initiate connection
  while (!client.connected()) {
    Serial.println("Initializing connection");
    // broadcast IP via UDP broadcast
    String ipAddress = WiFi.localIP().toString();
    udp.beginPacket(WiFi.broadcastIP().toString().c_str(), 2255);
    udp.println(ipAddress);
    udp.endPacket();
      
    // parse answer
    int packetSize;
    unsigned long startTime = millis();
    do {
      packetSize = udp.parsePacket();
    } while (!packetSize && (millis() - startTime) < 5000);
    if (packetSize) {
      // read answer which is IP adress
      char buffer[packetSize];
      Serial.println(udp.remoteIP().toString());
      udp.read(buffer, packetSize);
      String phoneIP = buffer;
      Serial.println(phoneIP); 

      // Establish a TCP connection to the phone
      client.connect(phoneIP.c_str(), 8080);

      // Send data to the phone
      client.println("Hello from the ESP32!");
    } else {
      Serial.println("Server dindn't answer...");
    }
  }
}

Android App相关代码

UDP通信类

public class Connectivity {
    private final Context context;
    private WifiManager wifiManager;
    private Handler handler;

    private DatagramSocket socket;


    public Connectivity(Context context) {

        this.context = context;
        this.handler = new Handler(Looper.getMainLooper());

        try {
            wifiManager = (WifiManager) this.context.getSystemService(Context.WIFI_SERVICE);
            socket = new DatagramSocket(2255);
            socket.setBroadcast(true);
        } catch (IOException e) {
            Log.e(TAG, "failed to create UDP socket", e);
        }
    }


    public void connectSensor(String ssid, String password) {

        // make sure wifi is enabled
        if (!wifiManager.isWifiEnabled()) {
            wifiManager.setWifiEnabled(true);
        }

        establishConAfterQ(ssid, password);
    }

    public void disconnectSensor() {
        if (wifiManager.isWifiEnabled()) {
            wifiManager.disconnect();
            wifiManager.setWifiEnabled(false);
        }
    }

    public void start() {
        new Thread(this::receive).start();
    }

    public void receive() {
        while (true) {
            byte[] buffer = new byte[128];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            try {
                socket.receive(packet);
            } catch (IOException e) {
                Log.e(TAG, "Failed to receive UDP packet", e);
                continue;
            }

            String message = new String(buffer, 0, packet.getLength());
            Log.d(TAG, "Received UDP packet: " + message.replace("\n", "") + " from IP address: " + packet.getAddress().toString() + " at port: " + packet.getPort());

            // Send the answer
            Log.i(TAG, "Sending Answer");
            String answer = "Heureka it works!!";
            byte[] data = answer.getBytes();
            DatagramPacket response = new DatagramPacket(data, data.length, packet.getAddress(), packet.getPort());

            try {
                socket.send(response);
            } catch (IOException e) {
                Log.e(TAG, "Failed to send UDP packet", e);
            }
        }
    }
}

WiFi连接函数

private void establishConAfterQ(String ssid, String password) {
        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
        config.enableTrustOnFirstUse(true);
        config.setPassword(password);
        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .build();
        NetworkRequest networkRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .setNetworkSpecifier(specifier)
                .build();

        ConnectivityManager connectivityManager = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);

        // Register a callback to listen for the connection result
        ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(android.net.Network network) {
                super.onAvailable(network);
                Log.d(TAG, "Connected to WiFi network: " + ssid);
            }

            @Override
            public void onUnavailable() {
                super.onUnavailable();
                Log.d(TAG, "Failed to connect to WiFi network: " + ssid);
            }
        };

        // Request the network connection
        connectivityManager.requestNetwork(networkRequest, networkCallback);
    }

解决建议

1. 绑定Socket到目标WiFi网络

使用ConnectivityManager.requestNetwork()连接WiFi时,Android不会自动让App的Socket切换到该网络,需要在onAvailable回调中绑定Socket:

@Override
public void onAvailable(android.net.Network network) {
    super.onAvailable(network);
    Log.d(TAG, "Connected to WiFi network: " + ssid);
    try {
        // 将UDP Socket绑定到当前连接的WiFi网络
        network.bindSocket(socket);
    } catch (IOException e) {
        Log.e(TAG, "Failed to bind socket to network", e);
    }
}

2. 检查并完善权限配置

确保AndroidManifest.xml中添加必要权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

注意:位置权限需要动态申请,否则无法正常扫描和连接WiFi。

3. 验证目标IP与端口

在日志中打印packet.getAddress()确认是否为ESP32的正确IP(192.168.1.1),若地址异常,可直接硬编码目标IP测试:

InetAddress esp32Ip = InetAddress.getByName("192.168.1.1");
DatagramPacket response = new DatagramPacket(data, data.length, esp32Ip, 2255);

4. 排查系统网络限制

关闭手机的"流量节省"、"私有DNS"功能,部分定制ROM的内置防火墙可能会拦截本地UDP流量,可在系统设置中允许App访问本地网络。

5. 避免端口冲突

ESP32和App均使用端口2255,可尝试让App使用随机端口发送应答(创建DatagramSocket时不指定端口),同时修改ESP32的UDP监听端口对应接收。


内容的提问来源于stack exchange,提问作者lordreas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:15:53