Android App向ESP32发送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

