您的位置:首页 > 移动开发 > Android开发

Android发送和接收UDP广播

2017-08-02 11:07 399 查看
要实现在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的分组(封包)时,它将被分发给该网段上的所有计算机。



[图片来自百度百科]

广播地址有两类:

受限广播

它不被路由发送,但会被送到相同物理网络段上的所有主机,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
}
}
}


声明权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>


Server:

发送广播包(kotlin):

class MainActivity : AppCompatActivity(), View.OnClickListener {

val TAG: String = MainActivity::class.java.simpleName
val SEND_PORT: Int = 8008
val DEST_PORT: Int = 8009
var isClosed: Boolean = false
var sendBuffer: String = "This is UDP Server"
lateinit var mSendBtn: Button
lateinit var mCloseBtn: Button
lateinit var mScrollView: ScrollView
lateinit var mLogTx: TextView
lateinit var mUDPBroadCast: UDPBroadcaster
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mUDPBroadCast = UDPBroadcaster(this)
initView()
initEvent()
}

private fun initView() {
mSendBtn = findViewById(R.id.btn_send) as Button
mCloseBtn = findViewById(R.id.btn_close) as Button
mScrollView = findViewById(R.id.scrollview) as ScrollView
mLogTx = findViewById(R.id.log) as TextView
}

private fun initEvent() {
mSendBtn.setOnClickListener(this)
mCloseBtn.setOnClickListener(this)
}

override fun onClick(v: View?) {
when (v?.id) {
R.id.btn_send -> sendUDPBroadcast()
R.id.btn_close -> closeUDPBroadcast()
}
}

private fun closeUDPBroadcast() {
isClosed = true
}

private fun sendUDPBroadcast() {
isClosed = false
mUDPBroadCast.open(SEND_PORT, DEST_PORT) //打开广播
val buffer: ByteArray = sendBuffer.toByteArray()
Thread(Runnable {
while (!isClosed) {
try {
Thread.sleep(500) //500ms 延时
} catch (e: Exception) {
e.printStackTrace()
}
mUDPBroadCast.sendPacket(buffer) //发送广播包
addLog("$TAG data: ${String(buffer)}")
}
mUDPBroadCast.close() //关闭广播
}).start()
}

private fun addLog(log: String) {
var mLog: String = log
if (mLog.endsWith("\n").not()) {
mLog += "\n"
}
mScrollView.post(Runnable {
mLogTx.append(mLog)
mScrollView.fullScroll(ScrollView.FOCUS_DOWN)
})
}
}


Client:

接收广播(kotlin):

class MainActivity : AppCompatActivity(), View.OnClickListener {

val TAG:String = MainActivity::class.java.simpleName
val LOCAL_PORT:Int = 8009
val DEST_PORT:Int = 8008
lateinit var mRecvBtn: Button
lateinit var mCloseBtn: Button
lateinit var mScrollView: ScrollView
lateinit var mLogTx: TextView
var isClosed:Boolean = false
lateinit var mUDPBroadCaster:UDPBroadcaster
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mUDPBroadCaster = UDPBroadcaster(this)
initView()
initEvent()
}

private fun initEvent() {
mRecvBtn.setOnClickListener(this)
mCloseBtn.setOnClickListener(this)
}

private fun initView() {
mRecvBtn = findViewById(R.id.btn_receive) as Button
mCloseBtn = findViewById(R.id.btn_close) as Button
mScrollView = findViewById(R.id.scrollView) as ScrollView
mLogTx = findViewById(R.id.log) as TextView

}
override fun onClick(v: View?) {
when(v?.id){
R.id.btn_receive->recvUDPBroadcast()
R.id.btn_close->cancelRecv()
}
}

private fun cancelRecv() {
isClosed = true
}

private fun recvUDPBroadcast() {
isClosed = false
mUDPBroadCaster.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()}
mUDPBroadCaster.recvPacket(packet) //接收广播
val data:String = String(packet.data)
addLog("$TAG data: $data")
addLog("$TAG addr: ${packet.address}")
addLog("$TAG port: ${packet.port}")
}
mUDPBroadCaster.close() //退出接收广播
}).start()
}
private fun addLog(log: String) {
var mLog: String = log
if (mLog.endsWith("\n").not()) {
mLog += "\n"
}
mScrollView.post(Runnable {
mLogTx.append(mLog)
mScrollView.fullScroll(ScrollView.FOCUS_DOWN)
})
}
}


代码很少,也很容易看懂。发送和接收广播的截图如下:

Server



Client



源码下载地址

Demo(CSDN下载)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息