Android发送和接收UDP广播

时间:2022-12-15 16:42:59

要实现在Android平台上发UDP广播,可能需要先了解一下什么是广播地址

广播地址

广播地址(Broadcast Address)是专门用于同时向网络中所有工作站进行发送的一个地址。在使用TCP/IP协议的网络中,主机标识端host ID 为全1的IP地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。例如,对于10.1.1.0(255.255.255.0)网段,其广播地址为10.1.1.255(255即为2进制的11111111),当发出一个目的地址为10.1.1.255的分组(封包)时,它将被分发给该网段上的所有计算机。

Android发送和接收UDP广播
[图片来自百度百科]

广播地址有两类:

  • 受限广播
    它不被路由发送,但会被送到相同物理网络段上的所有主机,IP地址的网络字段和主机字段全为1就是地址255.255.255.255。
  • 直接广播
    网络广播会被路由发送,并会发送到专门网络上的每台主机,IP地址的网络字段定义这个网络,主机字段通常为全为1,如 192.168.10.255

关于广播地址的其他知识,大家可以自行搜索学习。

当我们知道有广播地址这个东西之后,就能很方便地在Android平台上实现发送广播和接收广播了。

一台Android作为Server端发送广播,那么此时的广播地址怎么确定,因为,作为Server端的手机可能是连接到一个路由器上,也有可能是自己作为AP设备发热点,让Client端去连接。对于以上的两种情况,广播地址是有所不同的:

  • 第一种情况(server端连接到路由器):见下面代码片段。
  • 第二种情况(server端作为AP设备发送热点):
    在这种情况下,IP也是可以确定,有人在分析Android源码的时候,发现如果Android设备开启了wifi热点,那么,该设备的本地IP是固定的,是192.168.43.1,那么我们就可以知道此时对应的广播地址就是192.168.43.255。

通过以下代码可以获取到本地IP(java)

    public static String getLocalIPAddress() {
Enumeration<NetworkInterface> enumeration = null;
try {
enumeration = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
Logger.w(e);
}
if (enumeration != null) {
// 遍历所用的网络接口
while (enumeration.hasMoreElements()) {
NetworkInterface nif = enumeration.nextElement();// 得到每一个网络接口绑定的地址
Enumeration<InetAddress> inetAddresses = nif.getInetAddresses();
// 遍历每一个接口绑定的所有ip
if (inetAddresses != null)
while (inetAddresses.hasMoreElements()) {
InetAddress ip = inetAddresses.nextElement();
if (!ip.isLoopbackAddress() && isIPv4Address(ip.getHostAddress())) {
return ip.getHostAddress();
}
}
}
}
return "";
}
/**
* Ipv4 address check.
*/

private static final Pattern IPV4_PATTERN = Pattern.compile("^(" +
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" +
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");

/**
* Check if valid IPV4 address.
*
* @param input the address string to check for validity.
* @return True if the input parameter is a valid IPv4 address.
*/

public static boolean isIPv4Address(String input) {
return IPV4_PATTERN.matcher(input).matches();
}

通过以下代码可以直接获取广播地址,如果是打开wifi热点直接返回“192.168.43.255”(kotlin):

    companion object {
fun getBroadcastAddress(context: Context): InetAddress {
if (isWifiApEnabled(context)) { //判断wifi热点是否打开
return InetAddress.getByName("192.168.43.255") //直接返回
}
val wifi: WifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val dhcp: DhcpInfo = wifi.dhcpInfo ?: return InetAddress.getByName("255.255.255.255")
val broadcast = (dhcp.ipAddress and dhcp.netmask) or dhcp.netmask.inv()
val quads = ByteArray(4)
for (k in 0..3) {
quads[k] = ((broadcast shr k * 8) and 0xFF).toByte()
}
return InetAddress.getByAddress(quads)
}

/**
* check whether the wifiAp is Enable
*/

private fun isWifiApEnabled(context: Context): Boolean {
try {
val manager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val method = manager.javaClass.getMethod("isWifiApEnabled")
return method.invoke(manager) as Boolean
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
return false
}
}

如果,广播地址能确定了,下面就可以进行实现发送广播(Server)和接收广播(Client)了

我们先定义一个通用的UDP广播类:包括(获取广播地址,打开和关闭广播、发送广播包和接收广播包)

UDPBroadcaster.kt:(kotlin)

class UDPBroadcaster(var mContext: Context) {
private val TAG:String = UDPBroadcaster::class.java.simpleName
private var mDestPort = 0
private var mSocket: DatagramSocket? = null
private val ROOT_PATH:String = Environment.getExternalStorageDirectory().path
/**
* 打开
*/

fun open(localPort: Int, destPort: Int): Boolean {
mDestPort = destPort
try {
mSocket = DatagramSocket(localPort)
mSocket?.broadcast = true
mSocket?.reuseAddress = true
return true
} catch (e: SocketException) {
e.printStackTrace()
}
return false
}

/**
* 关闭
*/

fun close(): Boolean {
if (mSocket != null && mSocket?.isClosed?.not() as Boolean) {
mSocket?.close()
}
return true
}

/**
* 发送广播包
*/

fun sendPacket(buffer: ByteArray): Boolean {
try {
val addr = getBroadcastAddress(mContext)
Log.d("$TAG addr",addr.toString())
val packet = DatagramPacket(buffer, buffer.size)
packet.address = addr
packet.port = mDestPort
mSocket?.send(packet)
return true
} catch (e1: UnknownHostException) {
e1.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
return false
}

/**
* 接收广播
*/

fun recvPacket(buffer: ByteArray): Boolean {
val packet = DatagramPacket(buffer, buffer.size)
try {
mSocket?.receive(packet)
return true
} catch (e: IOException) {
e.printStackTrace()
}
return false
}

companion object {
/**
* 获取广播地址
*/

fun getBroadcastAddress(context: Context): InetAddress {
if (isWifiApEnabled(context)) { //判断wifi热点是否打开
return InetAddress.getByName("192.168.43.255") //直接返回
}
val wifi: WifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val dhcp: DhcpInfo = wifi.dhcpInfo ?: return InetAddress.getByName("255.255.255.255")
val broadcast = (dhcp.ipAddress and dhcp.netmask) or dhcp.netmask.inv()
val quads = ByteArray(4)
for (k in 0..3) {
quads[k] = ((broadcast shr k * 8) and 0xFF).toByte()
}
return InetAddress.getByAddress(quads)
}

/**
* check whether the wifiAp is Enable
*/

private fun isWifiApEnabled(context: Context): Boolean {
try {
val manager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val method = manager.javaClass.getMethod("isWifiApEnabled")
return method.invoke(manager) as Boolean
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
return false
}
}
}

Server:

发送广播包(kotlin):

 val SEND_PORT: Int = 8008
val DEST_PORT: Int = 8009
private fun sendPacket() {
isClosed = false
mBroadCast.open(SEND_PORT, DEST_PORT)
val buffer: ByteArray = sendBuffer.toByteArray()
Thread(Runnable {
while (!isClosed) {
try {
Thread.sleep(500) //500ms 发送一次广播
} catch (e: Exception) {
e.printStackTrace()
}
mBroadCast.sendPacket(buffer)
addLog("$TAG data: ${String(buffer)}")
}
mBroadCast.close()
}).start()
}

Client:

接收广播(kotlin):

 val LOCAL_PORT:Int = 8009
val DEST_PORT:Int = 8008
private fun recPacket() {
isClosed = false
mBroadCast.open(LOCAL_PORT,DEST_PORT)
var buffer:ByteArray = kotlin.ByteArray(1024)
val packet = DatagramPacket(buffer, buffer.size)
Thread(Runnable {
while (!isClosed){
try{
Thread.sleep(500) //500ms接收一次广播
}catch (e:Exception){e.printStackTrace()}
mBroadCast.recvPacket(packet)
this.port = packet.port.toString()
this.ip = packet.address.toString()
val data:String = String(packet.data)
addLog("$TAG data: $data")
addLog("$TAG addr: ${packet.address}")
addLog("$TAG port: ${packet.port}")
}
mBroadCast.close()
}).start()
}

以上就是Android平台发送广播和接收广播的关键代码。
后面我再补上demo代码。