Android发送和接收UDP广播

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

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值