旧机宝开发笔记之:局域网内android设备的发现和通信

旧机宝需要在一个局域网内(同一wifi下)实现设备的发现和通信功能。对于设备的发现,包括主动去搜寻别的设备以及主动声明自身的存在。对于通信则要求通信的句柄和设备的基本信息存储起来以供下次使用。实现的原理非常简单,创建socketserver,并通过UDP告诉其他设备该socketserver的存在,其他设备则通过连接该socketserver来维持通信链路。

1、设备发现

在所有设备都在同一网络并打开旧机宝的情况下,通过MulticastSocket向指定ip地址发送信息来声明自身存在,使用同一个MulticastSocket接收报文来发现其他设备。

private var multicastSocket: MulticastSocket = MulticastSocket(Constants.Port.udp_broadcast)
multicastSocket.joinGroup(inetAddress)
multicastSocket.loopbackMode = false

multicastSocket需要在应用中指定固定的端口号,并加入到指定的ip地址,以使不同的设备“加入到一个广播群组”中。

 fun sayHiToAll(){
        Thread(Runnable {
            if(TCPManager.port!=0){
                val builder= MyMessage.DeviceInfo.newBuilder()
                if(TextUtils.isEmpty(DataHelper.androidId))return@Runnable
                builder.androidId=DataHelper.androidId
                builder.ip=Utils.getIPAddress(MyApplication.context) ?: return@Runnable
                builder.connectedSize=TCPManager.connecteds.size
                builder.port=TCPManager.port
                builder.phoneBrand= Build.BRAND
                builder.phoneModel=Build.MODEL

                outDatagramPacket.data = MyMessage.Message.newBuilder().setDeviceInfo( builder.build()).setType(MessageType.UDP_HELLO).build().toByteArray()
                try {
                    multicastSocket.send(outDatagramPacket)
                    Log.i(TAG_DEVICE,"发送UDP信息:\n${builder.build()}")
                }catch (e:Exception){

                }

            }
        }).start()
    }

通过该multicastSocket将本机的一些基本信息(TCP的ip地址、端口等)发送到广播组里,从而通知到每一个加入进来的设备,实现对自身的声明。

fun startServer() {
        Log.i(TAG_DEVICE,"开启UDP服务")
        Thread(Runnable {
            while (true) {
                val inData = ByteArray(256)
                val datagramPacket = DatagramPacket(inData, inData.size)
                multicastSocket.receive(datagramPacket)
                val content=ByteArray(datagramPacket.length)
                System.arraycopy(inData,0,content,0,datagramPacket.length)
                val myMessage=MyMessage.Message.parseFrom(content)
                if(myMessage!=null){
                    if(myMessage.deviceInfo.androidId==DataHelper.androidId)continue
                    Log.i(TAG_DEVICE,"接收到UDP信息:\n$myMessage")
                    if(myMessage.type==MessageType.UDP_HELLO){
                        if(DataHelper.androidId > myMessage.deviceInfo.androidId) {
                            TCPManager.connect(myMessage)
                        } else {
                            sayHiToAll()
                            continue
                        }
                        if(TCPManager.connecteds.size<myMessage.deviceInfo.connectedSize) sayHiToAll()
                    }else{
                        EventBus.getDefault().post(myMessage)
                    }

                }

            }
        }).start()
    }

当实现对同一个multicastSocket的UDP消息监听的时候,应用就具备了接收其他设备声明自己的消息的能力,消息里有对方的ip地址和端口等有用信息。值得注意的是,我们也可以通过增加一些逻辑来实现:当接收到指定的UDP信息,就立刻将本机信息通过UDP发送到广播组。以此来主动要求在网设备暴露自己。

2、设备通信

通过设备发现(定时、用户触发等),我们已经可以获知当前网络有多少旧机宝设备(加入到特定ip地址的multicastSocket),现在可以创建稳定的通信连接了。其实在上面UDP信息发送之前,设备就已经创建了socketserver了,该服务的ip地址和端口通过UDP消息发出,从而让接收到UDP消息的设备,通过ip信息来和socketsever建立网络连接。这里有一个小插曲:同一网络下的多个设备,如果任意的两台设备之间,同时向对方发送UDP信息来要求对方连接自己,则有可能在两个设备之间创建两条通信线路,这明显多余并且不利于之后的使用。解决的方式有很多种,旧机宝采用比较androidid大小的方式,由androidid小的设备来连接androidid大的设备,这样就算两台设备之间同时发给对方UDP消息,他们之间被连接的“服务端”身份和发起连接的“客户端”身份也是指定的,不会同时连接对方。

if(DataHelper.androidId > myMessage.deviceInfo.androidId) {
                            TCPManager.connect(myMessage)
                        } 
fun startServer() {
        Log.i(Constants.LogTag.TAG_DEVICE,"开启TCP服务")

        Thread(Runnable {
            val serverSocket=ServerSocket(0)
            port=serverSocket.localPort
            while (true){
                val socket=serverSocket.accept()
                socket.keepAlive=true
                val inputStream=socket.getInputStream()
                val lengthBytes = ByteArray(4)
                if (inputStream.read(lengthBytes) != 4) return@Runnable
                val length = bytes2Int(lengthBytes)
                val content = ByteArray(length)
                if(inputStream.read(content) <= 0)return@Runnable
                val socketMessage=MyMessage.Message.parseFrom(content)
                if(socketMessage!=null && socketMessage.type==MessageType.TCP_HELLO){
                    addConnected(socketMessage.deviceInfo.androidId)
                    val phoneDevice=PhoneDevice()
                    phoneDevice.deviceInfo=socketMessage.deviceInfo
                    phoneDevice.functionId=socketMessage.dataLong.toInt()
                    phoneDevice.socket=socket
                    DeviceManager.addDevice(phoneDevice)
                    sayTCPHi(socket)
                }

            }

        }).start()
    }

首先启动的socketserver服务会一直等待别人的连接,期间UDP消息正在大肆宣扬该socketserver的存在。

fun connect(message: MyMessage.Message) {
        val deviceInfo=message.deviceInfo
        if(TextUtils.isEmpty(deviceInfo.androidId))return
        if(isConnecting(deviceInfo.androidId) || isConnected(deviceInfo.androidId))return
        addConnecting(deviceInfo.androidId)
        Log.i(Constants.LogTag.TAG_DEVICE,"开始连接:\n$deviceInfo")
        val socket: Socket?
        try {
            socket=Socket(deviceInfo.ip,deviceInfo.port)
        }finally {
            removeConnecting(deviceInfo.androidId)
        }
        if (socket==null)return
        socket.keepAlive=true
        sayTCPHi(socket)

        val inputStream=socket.getInputStream()
        val lengthBytes = ByteArray(4)
        if (inputStream.read(lengthBytes) != 4) return
        val length = bytes2Int(lengthBytes)
        val content = ByteArray(length)
        if(inputStream.read(content) <= 0)return
        val socketMessage=MyMessage.Message.parseFrom(content)
        if(socketMessage!=null && socketMessage.type==MessageType.TCP_HELLO){
            addConnected(socketMessage.deviceInfo.androidId)
            val phoneDevice=PhoneDevice()
            phoneDevice.deviceInfo=socketMessage.deviceInfo
            phoneDevice.functionId=socketMessage.dataLong.toInt()
            phoneDevice.socket=socket
            DeviceManager.addDevice(phoneDevice)
        }
//        val phoneDevice=PhoneDevice()
//        phoneDevice.deviceInfo=deviceInfo
//        phoneDevice.socket=socket
//        DeviceManager.addDevice(phoneDevice)
//
//        sayTCPHi(socket)
    }

接收到UDP信息的设备连接该socketserver,使得两台设备都拿到对应的socket,对该socket保存留作后用,至此设备的通信功能实现了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值