Socket广播包经常被用于局域网内的两台设备之间互相发现和消息传递,在Android应用开发过程中,也经常会遇到这样的需求,例如:两台Android设备之间、Android与手环等智能硬件之间、Android与Windows电脑之间等等。

普通WIFI广播示例

首先给出一段Android发送广播包的示例代码

DatagramSocket socket = new DatagramSocket(8000);
socket.setBroadcast(true);
InetAddress addr = InetAddress.getByName("255.255.255.255");
byte[] buffer = "Hello World".getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
packet.setAddress(addr);
packet.setPort(8086);
socket.send(packet);

下面分析其中需要注意的地方:

  • 不要在主线程中发送广播包
  • 广播地址不建议使用”255.255.255.255”

255.255.255.255 是一种受限的广播地址,常用于在计算机不知道自己IP地址的时候发送,比如设备启动时向DHCP服务器索要地址等等,一般情况下,路由器不会转发目标为受限广播地址的广播包。

而且,有些路由器/Wi-Fi热点不支持该广播地址(例如:用Android手机做Wi-Fi热点的时候),因此在程序中会出现“ENETUNREACH (Network is unreachable)”的异常,因此,为了保证程序成功发送广播包,建议使用直接广播地址,例如:当前IP地址是 192.168.1.100,子网掩码是 255.255.255.0 的情况下,广播地址为:192.168.1.255,(具体的推算方法这里就不展开了,可以参考计算机网络相关书籍)。

那么,如何得到本网段的直接广播地址呢,下面是stackoverflow上面有位大牛分享的代码:

public static InetAddress getBroadcastAddress(Context context) throws UnknownHostException {
    WifiManager wifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
    DhcpInfo dhcp = wifi.getDhcpInfo();
    if(dhcp==null) {
        return InetAddress.getByName("255.255.255.255");
    }
    int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
    byte[] quads = new byte[4];
    for (int k = 0; k < 4; k++)
        quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
    return InetAddress.getByAddress(quads);
}

直接使用该函数即可得到正确的“广播地址”,通过setAddress函数设置到DatagramPacket对象中即可。

Android设置为Wi-Fi热点时的广播地址

这是个比较大的坑,当Android设备被设置为Wi-Fi热点的时候,上面的函数得到的地址是”0.0.0.0”,因此,我们需要探究当Android设备被设置为Wi-Fi热点的时候,它的IP地址究竟是多少?

有人研究了Android底层源码发现,当Android设备被设置为Wi-Fi热点的时候,其IP地址是hardcode写死在源码中的,地址是:“192.168.43.1”,对应的广播地址是:”192.168.43.255”

为此,我们需要写个函数来判断一下当前Android手机是否处于Wi-Fi热点模式下,如果是,则应该使用上面给出的这个广播地址,这里给出代码示例:

protected static Boolean isWifiApEnabled(Context context) {
    try {
        WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
        Method method = manager.getClass().getMethod("isWifiApEnabled");
        return (Boolean)method.invoke(manager);
    }
    catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)  {
        e.printStackTrace();
}
    return false;
}

Android SDK并没有开放判断是否处于热点模式的API,因此,我们需要通过反射的方式来得到,另外,注意添加权限.

但是在部分手机上,热点的广播地址不再是 『192.168.43.1』 了,这个时候需要自己去获取广播地址:

private static InetAddress getBroadcastAddress() {
    try {
        Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
                .getNetworkInterfaces();
        while (enumNetworkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = enumNetworkInterfaces.nextElement();
            Enumeration<InetAddress> enumInetAddress = networkInterface
                    .getInetAddresses();

            while (enumInetAddress.hasMoreElements()) {
                InetAddress inetAddress = enumInetAddress.nextElement();
                if (inetAddress.isSiteLocalAddress() && !inetAddress.isLoopbackAddress()) {
                    List<InterfaceAddress> addresses = networkInterface.getInterfaceAddresses();

                    for (InterfaceAddress address: addresses) {
                        if (address.getBroadcast() != null) {
                            return address.getBroadcast();
                        }
                    }
                }
            }
        }
    } catch (SocketException e) {
        e.printStackTrace();
    }
    return null;
}

那么最后完整的获取广播地址的代码如下:

public static InetAddress getBroadcastAddress(Context context) throws UnknownHostException {
    if (isWifiApEnabled(context)) {
        InetAddress broadcast = BroadcastAddressUtils.getBroadcastAddress();
        if (broadcast != null) {
            return broadcast;
        }
        return InetAddress.getByName("192.168.43.255");
    }
    WifiManager wifi = (WifiManager) context.getApplicationContext()
            .getSystemService(Context.WIFI_SERVICE);
    DhcpInfo dhcp = wifi.getDhcpInfo();
    if (dhcp == null) {
        return InetAddress.getByName("255.255.255.255");
    }
    int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
    byte[] quads = new byte[4];
    for (int k = 0; k < 4; k++) {
        quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
    }
    return InetAddress.getByAddress(quads);
}

参考

https://blog.csdn.net/Ltp_zy/article/details/79666272

https://stackoverflow.com/questions/14080573/getting-wifi-broadcast-address-in-android-wifi-hotspot