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

Android(5.0+)Java实现局域网内ESP32 WebServer动态IPv4快速查找方案

解决方案:Android 5.0+ 局域网ESP32 WebServer IP发现

针对Android 12以下版本,提供三个比全段遍历更高效可靠的方案,覆盖路由器、本机热点、他人热点等所有局域网场景:


方案1:自定义UDP广播/组播主动发现(推荐,最快最可靠)

核心逻辑

ESP32端监听指定UDP端口的广播/组播包,收到后立即回复自身IPv4地址和WebServer标识;Android端发送广播/组播请求,接收ESP32的响应即可获取目标IP,无需遍历。

ESP32代码片段(Arduino框架)

#include <WiFi.h>
#include <WiFiUdp.h>

WiFiUDP udp;
const int UDP_PORT = 12345;
const String DEVICE_ID = "ESP32_WEBSERVER"; // 自定义设备标识,用于校验响应

void setup() {
  WiFi.begin("SSID", "PASSWORD");
  while (WiFi.status() != WL_CONNECTED) delay(500);
  
  udp.begin(UDP_PORT);
  Serial.println("UDP listener started on port " + String(UDP_PORT));
}

void loop() {
  int packetSize = udp.parsePacket();
  if (packetSize) {
    char incomingPacket[255];
    int len = udp.read(incomingPacket, 255);
    if (len > 0) incomingPacket[len] = 0;
    
    // 判断是否是Android端的发现请求
    if (strcmp(incomingPacket, "DISCOVER_ESP32") == 0) {
      String response = DEVICE_ID + ":" + WiFi.localIP().toString();
      udp.beginPacket(udp.remoteIP(), udp.remotePort());
      udp.print(response);
      udp.endPacket();
    }
  }
  delay(10);
}

Android代码片段(Java)

import android.os.AsyncTask;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Esp32DiscoveryTask extends AsyncTask<Void, Void, String> {
    private static final int UDP_PORT = 12345;
    private static final String DISCOVER_MSG = "DISCOVER_ESP32";
    private static final String DEVICE_ID = "ESP32_WEBSERVER";
    private DiscoveryCallback callback;

    public interface DiscoveryCallback {
        void onIpFound(String ip);
        void onFailure();
    }

    public Esp32DiscoveryTask(DiscoveryCallback callback) {
        this.callback = callback;
    }

    @Override
    protected String doInBackground(Void... voids) {
        try (DatagramSocket socket = new DatagramSocket()) {
            socket.setBroadcast(true);
            socket.setSoTimeout(3000); // 超时3秒
            
            // 发送广播请求
            byte[] sendData = DISCOVER_MSG.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(
                sendData, sendData.length,
                InetAddress.getByName("255.255.255.255"), UDP_PORT
            );
            socket.send(sendPacket);
            
            // 接收响应
            byte[] receiveData = new byte[255];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            socket.receive(receivePacket);
            
            String response = new String(receivePacket.getData()).trim();
            if (response.startsWith(DEVICE_ID + ":")) {
                return response.split(":")[1];
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String ip) {
        if (ip != null) {
            callback.onIpFound(ip);
        } else {
            callback.onFailure();
        }
    }
}

// 使用示例
new Esp32DiscoveryTask(new Esp32DiscoveryTask.DiscoveryCallback() {
    @Override
    public void onIpFound(String ip) {
        // 拿到IP后进行HTTP请求
    }

    @Override
    public void onFailure() {
        // 处理失败逻辑,比如 fallback 到ARP优化遍历
    }
}).execute();

方案2:ARP缓存优化遍历(无需修改ESP32)

核心逻辑

读取Android系统的ARP缓存文件/proc/net/arp,获取局域网内已活跃的设备IP,仅对这些IP发送HTTP请求,避免全段255个IP的无效扫描,大幅缩短耗时。

Android代码片段(Java)

import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ArpScanTask extends AsyncTask<Void, String, String> {
    private static final String ESP32_WEB_PATH = "/api/device-info"; // ESP32 WebServer的校验路径
    private static final int HTTP_TIMEOUT = 500;
    private ScanCallback callback;

    public interface ScanCallback {
        void onIpFound(String ip);
        void onScanComplete();
    }

    public ArpScanTask(ScanCallback callback) {
        this.callback = callback;
    }

    @Override
    protected String doInBackground(Void... voids) {
        List<String> ipList = getLocalNetworkIpsFromArp();
        for (String ip : ipList) {
            if (isEsp32WebServer(ip)) {
                return ip;
            }
        }
        return null;
    }

    // 从ARP缓存读取活跃IP
    private List<String> getLocalNetworkIpsFromArp() {
        List<String> ipList = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"))) {
            String line;
            br.readLine(); // 跳过表头
            while ((line = br.readLine()) != null) {
                String[] parts = line.split("\\s+");
                if (parts.length >= 1) {
                    String ip = parts[0];
                    // 排除本机IP和广播IP
                    if (!ip.equals("127.0.0.1") && !ip.equals("255.255.255.255")) {
                        ipList.add(ip);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ipList;
    }

    // 校验是否是目标ESP32 WebServer
    private boolean isEsp32WebServer(String ip) {
        try {
            URL url = new URL("http://" + ip + ESP32_WEB_PATH);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(HTTP_TIMEOUT);
            conn.setReadTimeout(HTTP_TIMEOUT);
            conn.setRequestMethod("GET");
            int responseCode = conn.getResponseCode();
            return responseCode == 200; // 或者校验响应内容
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    protected void onPostExecute(String ip) {
        if (ip != null) {
            callback.onIpFound(ip);
        }
        callback.onScanComplete();
    }
}

方案3:第三方mDNS库实现(兼容ESP32原生mDNS)

核心逻辑

Android 12以下系统不支持系统级mDNS,但可以使用第三方库(如JmDNS)实现mDNS解析,直接发现ESP32的mDNS服务(无需修改ESP32的mDNS配置)。

配置与代码

  1. 添加Gradle依赖:
dependencies {
    implementation 'javax.jmdns:jmdns:3.5.5'
}
  1. Android代码片段(Java):
import android.os.AsyncTask;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import java.net.InetAddress;

public class MdnsDiscoveryTask extends AsyncTask<Void, Void, String> {
    private static final String SERVICE_TYPE = "_http._tcp.local."; // ESP32 mDNS服务类型
    private static final String SERVICE_NAME = "ESP32-WebServer"; // ESP32设置的mDNS服务名
    private DiscoveryCallback callback;
    private JmDNS jmdns;

    public interface DiscoveryCallback {
        void onIpFound(String ip);
        void onFailure();
    }

    public MdnsDiscoveryTask(DiscoveryCallback callback) {
        this.callback = callback;
    }

    @Override
    protected String doInBackground(Void... voids) {
        try {
            InetAddress localAddr = getLocalIpAddress();
            jmdns = JmDNS.create(localAddr);
            
            jmdns.addServiceListener(SERVICE_TYPE, new ServiceListener() {
                @Override
                public void serviceAdded(ServiceEvent event) {
                    jmdns.requestServiceInfo(event.getType(), event.getName(), 1000);
                }

                @Override
                public void serviceRemoved(ServiceEvent event) {}

                @Override
                public void serviceResolved(ServiceEvent event) {
                    if (event.getName().equals(SERVICE_NAME)) {
                        String ip = event.getInfo().getInetAddresses()[0].getHostAddress();
                        publishProgress(ip);
                        jmdns.close();
                    }
                }
            });
            
            Thread.sleep(5000); // 等待5秒获取服务
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jmdns != null) jmdns.close();
        }
        return null;
    }

    @Override
    protected void onProgressUpdate(String... values) {
        callback.onIpFound(values[0]);
    }

    @Override
    protected void onPostExecute(String ip) {
        if (ip == null) {
            callback.onFailure();
        }
    }

    // 获取本机局域网IP
    private InetAddress getLocalIpAddress() throws Exception {
        // 实现获取本机IPv4地址的逻辑,此处省略,可通过WifiManager获取
        return InetAddress.getByName("192.168.43.1"); // 示例IP,需替换为真实逻辑
    }
}

方案选择建议

  • 优先选方案1:主动发现,耗时最短(毫秒级),兼容性最好,仅需ESP32端少量修改。
  • 若无法修改ESP32,选方案2:基于ARP缓存的扫描,比全段遍历快5-10倍。
  • 若ESP32已配置mDNS,选方案3:无需修改ESP32,利用第三方库实现mDNS解析。

内容的提问来源于stack exchange,提问作者dev.tejasb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:58:12