Udp广播发现局域网设备

最近在做一个项目,手机app需要发现家庭Wi-Fi下面连接的物联网设备,并获取设备的一些相关信息,思考了几种方案,最终决定使用Udp广播的形式,理由呢,就是Udp使用起来简单,大部分功能Google已经替我们封装好了,直接使用就可以。
很多人说Udp是不可靠的,因为它是一种无连接协议。但是考虑到使用的场景:家庭Wi-Fi,网络环境不会太复杂;每次发送的数据很小等等,我觉得Udp能够满足需求,好了,废话不说,直接上代码。
首先是手机app端,app主动发送Udp广播,并监听指定端口来接收设备单播回来的数据,这里我使用两个线程,一个负责发广播,一个负责接收数据,考虑到可能多个地方会使用到,我决定封装成为一个工具类,关键代码如下:
/**
 * 用来发送Udp广播
 * @param sendData:需要广播出去的数据
 * */
public void send(final UdpScanSendData sendData) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                DatagramSocket hostSocket = null;
                try {
                    hostSocket = new DatagramSocket();
                    Gson gson = new Gson();
                    //设置30秒超时
                    hostSocket.setSoTimeout(30000);
                    //转换为json字符串
                    String req = gson.toJson(sendData);
                    Log.e("UDP req", req);
                    //转换为byte数组
                    byte[] data = req.getBytes();
                    //设置广播地址
                    InetAddress ipBroad = InetAddress.getByName("255.255.255.255");
                    DatagramPacket packet = new DatagramPacket(data, data.length, ipBroad, 2088);
                    packet.setData(data);
                    //发送数据
                    hostSocket.send(packet);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (hostSocket != null) {
                        hostSocket.close();
                    }
                }
            }
        }).start();
    }

其中UdpScanSendData 包含了手机端的一些信息,比如手机的Ip地址,监听的端口(这里为2088),以及会话标识,代码如下:

public class UdpScanSendData {

    public String IP;
    public String port;
    public String msgType;

    public UdpScanSendData(String IP) {
        this.IP = IP;
        this.port = 2088;
        this.msgType = "SCAN_DEV_REQ";
    } 
}

这样,我们就通过广播,把手机的信息广播出去,当设备收到广播,解析出手机的Ip,端口号,就可以把设备信息,通过单播的形式发送到手机,因此手机还需要监听指定的端口,来接收数据,代码如下:

  /**
     * 监听Udp回信
     * @param handler 使用handler把接收到的消息传递出去
     * @param localIp 手机Ip地址
     * */
    public void receive(final String localIp, final Handler handler) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (handler != null) {
                    handler.sendEmptyMessage(HttpConstance.SCANING);
                }
                byte[] data = new byte[1024 * 4];
                datagramSocket = null;
                DatagramPacket dp;
                //这里存放接收到的对象
                Set<UdpScanReceiveData> set = new HashSet<>();
                try {
                    datagramSocket = new DatagramSocket(null);
                    datagramSocket.setReuseAddress(true);
                    //绑定指定端口
                    datagramSocket.bind(new InetSocketAddress(2017));
                    datagramSocket.setSoTimeout(30000);
                    dp = new DatagramPacket(data, data.length);
                    while (!datagramSocket.isClosed()) {
                        //接收消息
                        datagramSocket.receive(dp);
                        if (dp != null) {
                            //接收到数据包的ip地址
                            String devIp = dp.getAddress().getHostAddress();
                            //过滤本机的Ip地址,由于是发的全网广播,手机也可能会收到
                            if (!localIp.equals(devIp)) {
                                //还原出消息字符串
                                String rsp = new String(dp.getData(), dp.getOffset(), dp.getLength());
                                Log.e("接收到 ==", rsp);
                                if (!TextUtils.isEmpty(rsp)) {
                                    //转换为指定的消息对象
                                    UdpScanReceiveData rspData = new Gson().fromJson(rsp, UdpScanReceiveData.class);
                                    if (rspData != null) {
                                        //相关的逻辑处理
                                        set.add(rspData);
                                            if (handler != null) {
                                                handler.obtainMessage(HttpConstance.SCAN_SUCCESS, set).sendToTarget();
                                            }
                                    }
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (handler != null) {
                        if (set.size() > 0) {
                            handler.obtainMessage(HttpConstance.SCAN_COMPLETE, set).sendToTarget();
                        } else {
                            handler.obtainMessage(HttpConstance.SCAN_FAILD).sendToTarget();
                        }
                    }
                    if (datagramSocket != null) {
                        datagramSocket.close();
                        datagramSocket = null;
                    }
                }
            }
        }).start();
    }

我把这个工具类,定义为单例,把datagramSocket定义为全局变量,这样方便我们在外面关闭upd监听,从上面的代码可以看出,要关闭Udp监听,要么等30秒超时,要么调用datagramSocket.close()这个方法,为此,我们添加一个关闭Udp的方法:

 public void close() {
        try {
            if (datagramSocket != null) {
                datagramSocket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            datagramSocket = null;
        }
    }

这样,手机端的工具类我们就封装好了。到这里,我的工作算是完成了,剩下的就交给终端厂商完成,在手机app上面,点击扫描按钮,就调用工具类的send方法发送一个Udp广播,并监听端口30秒,当设备接收到该广播,就是向手机的ip地址,端口发送一个udp单播,把终端数据发送给手机app。
因为udp的不可靠性,我在activity里面定义了一个子线程,每隔5秒调用一次发送广播的方法,实验表明,发送3次,基本上不会出现扫描不到的情况。
客户端的流程跟手机app刚好相反,开启一个线程监听约定好的端口,这里是2088,当收到广播时候,如果会话标识正确,就把自己的信息广播出去,附上客户端的测试demo,真实设备不是安卓的,该demo仅用于测试
https://github.com/youxibanlv/udpDemo.git

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值