Android发送和接收UDP广播

来源:互联网 发布:华为无线优化工程师 编辑:程序博客网 时间:2024/05/16 16:00

要实现在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下载)