Android 手机实现网络嗅探发现周围设备(类似悟空遥控器)

前几天接触过一个安卓手机遥控安卓电视的需求,需要完成局域网嗅探功能,搜索局域网内的电视设备,当时试着用socket连接轮流尝试,发现时间比较长,后来在网上发现一篇文章,用UDP实现这个功能,局域网广播的方式,效果不错,亲测可行,现贡献出代码。贴上后直接可以运行。

 

遥控器端代码:

import android.util.Log;


import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

import java.net.SocketTimeoutException;

import java.nio.charset.Charset;

import java.util.HashSet;

import java.util.Set;


public abstract class SearchThread extends Thread {

    private static final String TAG = SearchThread.class.getSimpleName();


    private static final int DEVICE_FIND_PORT = 9000;

    private static final int RECEIVE_TIME_OUT = 1500; // 接收超时时间

    private static final int RESPONSE_DEVICE_MAX = 200; // 响应设备的最大个数,防止UDP广播攻击


    private static final byte PACKET_TYPE_FIND_DEVICE_REQ_10 = 0x10; // 搜索请求

    private static final byte PACKET_TYPE_FIND_DEVICE_RSP_11 = 0x11; // 搜索响应

    private static final byte PACKET_TYPE_FIND_DEVICE_CHK_12 = 0x12; // 搜索确认


    private static final byte PACKET_DATA_TYPE_DEVICE_NAME_20 = 0x20;

    private static final byte PACKET_DATA_TYPE_DEVICE_ROOM_21 = 0x21;


    private DatagramSocket hostSocket;

    private Set<DeviceBean> mDeviceSet;


    private byte mPackType;

    private String mDeviceIP;


    SearchThread() {

        mDeviceSet = new HashSet<>();

    }


    @Override

    public void run() {

        try {

            onSearchStart();

            hostSocket = new DatagramSocket();

            // 设置接收超时时间

            hostSocket.setSoTimeout(RECEIVE_TIME_OUT);


            byte[] sendData = new byte[1024];

            InetAddress broadIP = InetAddress.getByName("255.255.255.255");

            DatagramPacket sendPack = new DatagramPacket(sendData, sendData.length, broadIP, DEVICE_FIND_PORT);


            for (int i = 0; i < 3; i++) {

                // 发送搜索广播

                mPackType = PACKET_TYPE_FIND_DEVICE_REQ_10;

                sendPack.setData(packData(i + 1));

                hostSocket.send(sendPack);


                // 监听来信

                byte[] receData = new byte[1024];

                DatagramPacket recePack = new DatagramPacket(receData, receData.length);

                try {

                    // 最多接收200个,或超时跳出循环

                    int rspCount = RESPONSE_DEVICE_MAX;

                    while (rspCount-- > 0) {

                        recePack.setData(receData);

                        hostSocket.receive(recePack);

                        if (recePack.getLength() > 0) {

                            mDeviceIP = recePack.getAddress().getHostAddress();

                            if (parsePack(recePack)) {

                                Log.i(TAG, "@@@zjun: 设备上线:" + mDeviceIP);

                                // 发送一对一的确认信息。使用接收报,因为接收报中有对方的实际IP,发送报时广播IP

                                mPackType = PACKET_TYPE_FIND_DEVICE_CHK_12;

                                recePack.setData(packData(rspCount)); // 注意:设置数据的同时,把recePack.getLength()也改变了

                                hostSocket.send(recePack);

                            }

                        }

                    }

                } catch (SocketTimeoutException e) {

                }

                Log.i(TAG, "@@@zjun: 结束搜索" + i);

            }

            onSearchFinish(mDeviceSet);

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            if (hostSocket != null) {

                hostSocket.close();

            }

        }


    }


    /**

     * 搜索开始时执行

     */

    public abstract void onSearchStart();


    /**

     * 搜索结束后执行

     *

     * @param deviceSet 搜索到的设备集合

     */

    public abstract void onSearchFinish(Set deviceSet);


    /**

     * 解析报文

     * 协议:$ + packType(1) + data(n)

     * data: n组数据,每组的组成结构type(1) + length(4) + data(length)

     * type类型中包含nameroom类型,但name必须在最前面

     */

    private boolean parsePack(DatagramPacket pack) {

        if (pack == null || pack.getAddress() == null) {

            return false;

        }


        String ip = pack.getAddress().getHostAddress();

        int port = pack.getPort();

        for (DeviceBean d : mDeviceSet) {

            if (d.getIp().equals(ip)) {

                return false;

            }

        }

        int dataLen = pack.getLength();

        int offset = 0;

        byte packType;

        byte type;

        int len;

        DeviceBean device = null;


        if (dataLen < 2) {

            return false;

        }

        byte[] data = new byte[dataLen];

        System.arraycopy(pack.getData(), pack.getOffset(), data, 0, dataLen);


        if (data[offset++] != '$') {

            return false;

        }


        packType = data[offset++];

        if (packType != PACKET_TYPE_FIND_DEVICE_RSP_11) {

            return false;

        }


        while (offset + 5 < dataLen) {

            type = data[offset++];

            len = data[offset++] & 0xFF;

            len |= (data[offset++] << 8);

            len |= (data[offset++] << 16);

            len |= (data[offset++] << 24);


            if (offset + len > dataLen) {

                break;

            }

            switch (type) {

                case PACKET_DATA_TYPE_DEVICE_NAME_20:

                    String name = new String(data, offset, len, Charset.forName("UTF-8"));

                    device = new DeviceBean();

                    device.setName(name);

                    device.setIp(ip);

                    device.setPort(port);

                    break;

                case PACKET_DATA_TYPE_DEVICE_ROOM_21:

                    String room = new String(data, offset, len, Charset.forName("UTF-8"));

                    if (device != null) {

                        device.setRoom(room);

                    }

                    break;

                default:

                    break;

            }

            offset += len;

        }

        if (device != null) {

            mDeviceSet.add(device);

            return true;

        }

        return false;

    }


    /**

     * 打包搜索报文

     * 协议:$ + packType(1) + sendSeq(4) + [deviceIP(n<=15)]

     * packType - 报文类型

     * sendSeq - 发送序列

     * deviceIP - 设备IP,仅确认时携带

     */

    private byte[] packData(int seq) {

        byte[] data = new byte[1024];

        int offset = 0;


        data[offset++] = '$';


        data[offset++] = mPackType;


        seq = seq == 3 ? 1 : ++seq; // can't use findSeq++

        data[offset++] = (byte) seq;

        data[offset++] = (byte) (seq >> 8);

        data[offset++] = (byte) (seq >> 16);

        data[offset++] = (byte) (seq >> 24);


        if (mPackType == PACKET_TYPE_FIND_DEVICE_CHK_12) {

            byte[] ips = mDeviceIP.getBytes(Charset.forName("UTF-8"));

            System.arraycopy(ips, 0, data, offset, ips.length);

            offset += ips.length;

        }


        byte[] result = new byte[offset];

        System.arraycopy(data, 0, result, 0, offset);

        return result;

    }



    /**

     * 设备Bean

     * 只要IP一样,则认为是同一个设备

     */

    public static class DeviceBean {

        String ip;      // IP地址

        int port;       // 端口

        String name;    // 设备名称

        String room;    // 设备所在房间


        @Override

        public int hashCode() {

            return ip.hashCode();

        }


        @Override

        public boolean equals(Object o) {

            if (o instanceof DeviceBean) {

                return this.ip.equals(((DeviceBean) o).getIp());

            }

            return super.equals(o);

        }


        public String getIp() {

            return ip;

        }


        public void setIp(String ip) {

            this.ip = ip;

        }


        public int getPort() {

            return port;

        }


        public void setPort(int port) {

            this.port = port;

        }


        public String getName() {

            return name;

        }


        public void setName(String name) {

            this.name = name;

        }


        public String getRoom() {

            return room;

        }


        public void setRoom(String room) {

            this.room = room;

        }

    }

}


电视盒子或电视端代码:


import android.content.Context;

import android.net.wifi.WifiInfo;

import android.net.wifi.WifiManager;

import android.util.Log;


import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetSocketAddress;

import java.net.SocketTimeoutException;

import java.nio.charset.Charset;


public abstract class WaitingSearch extends Thread {

    private final String TAG = WaitingSearch.class.getSimpleName();


    private static final int DEVICE_FIND_PORT = 9000;

    private static final int RECEIVE_TIME_OUT = 1500; // 接收超时时间,应小于等于主机的超时时间1500

    private static final int RESPONSE_DEVICE_MAX = 200; // 响应设备的最大个数,防止UDP广播攻击


    private static final byte PACKET_TYPE_FIND_DEVICE_REQ_10 = 0x10; // 搜索请求

    private static final byte PACKET_TYPE_FIND_DEVICE_RSP_11 = 0x11; // 搜索响应

    private static final byte PACKET_TYPE_FIND_DEVICE_CHK_12 = 0x12; // 搜索确认


    private static final byte PACKET_DATA_TYPE_DEVICE_NAME_20 = 0x20;

    private static final byte PACKET_DATA_TYPE_DEVICE_ROOM_21 = 0x21;


    private Context mContext;

    private String deviceName, deviceRoom;


    public WaitingSearch(Context context, String name, String room) {

        mContext = context;

        deviceName = name;

        deviceRoom = room;

    }


    @Override

    public void run() {

        DatagramSocket socket = null;

        try {

            socket = new DatagramSocket(DEVICE_FIND_PORT);

            byte[] data = new byte[1024];

            DatagramPacket pack = new DatagramPacket(data, data.length);

            while (true) {

                // 等待主机的搜索

                socket.receive(pack);

                if (verifySearchData(pack)) {

                    byte[] sendData = packData();

                    DatagramPacket sendPack = new DatagramPacket(sendData, sendData.length, pack.getAddress(), pack.getPort());

                    Log.i(TAG, "@@@zjun: 给主机回复信息");

                    socket.send(sendPack);

                    Log.i(TAG, "@@@zjun: 等待主机接收确认");

                    socket.setSoTimeout(RECEIVE_TIME_OUT);

                    try {

                        socket.receive(pack);

                        if (verifyCheckData(pack)) {

                            Log.i(TAG, "@@@zjun: 确认成功");

                            onDeviceSearched((InetSocketAddress) pack.getSocketAddress());

                            break;

                        }

                    } catch (SocketTimeoutException e) {

                    }

                    socket.setSoTimeout(0); // 连接超时还原成无穷大,阻塞式接收

                }

            }

        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            if (socket != null) {

                socket.close();

            }

        }

    }


    /**

     * 当设备被发现时执行

     */

    public abstract void onDeviceSearched(InetSocketAddress socketAddr);


    /**

     * 打包响应报文

     * 协议:$ + packType(1) + data(n)

     *  data: n组数据,每组的组成结构type(1) + length(4) + data(length)

     *  type类型中包含nameroom类型,但name必须在最前面

     */

    private byte[] packData() {

        byte[] data = new byte[1024];

        int offset = 0;

        data[offset++] = '$';

        data[offset++] = PACKET_TYPE_FIND_DEVICE_RSP_11;


        byte[] temp = getBytesFromType(PACKET_DATA_TYPE_DEVICE_NAME_20, deviceName);

        System.arraycopy(temp, 0, data, offset, temp.length);

        offset += temp.length;


        temp = getBytesFromType(PACKET_DATA_TYPE_DEVICE_ROOM_21, deviceRoom);

        System.arraycopy(temp, 0, data, offset, temp.length);

        offset += temp.length;


        byte[] retVal = new byte[offset];

        System.arraycopy(data, 0, retVal, 0, offset);


        return retVal;

    }


    private byte[] getBytesFromType(byte type, String val) {

        byte[] retVal = new byte[0];

        if (val != null) {

            byte[] valBytes = val.getBytes(Charset.forName("UTF-8"));

            retVal = new byte[5 + valBytes.length];

            retVal[0] = type;

            retVal[1] = (byte) valBytes.length;

            retVal[2] = (byte) (valBytes.length >> 8 );

            retVal[3] = (byte) (valBytes.length >> 16);

            retVal[4] = (byte) (valBytes.length >> 24);

            System.arraycopy(valBytes, 0, retVal, 5, valBytes.length);

        }

        return retVal;

    }


    /**

     * 校验搜索数据

     * 协议:$ + packType(1) + sendSeq(4)

     *  packType - 报文类型

     *  sendSeq - 发送序列

     */

    private boolean verifySearchData(DatagramPacket pack) {

        if (pack.getLength() != 6) {

            return false;

        }


        byte[] data = pack.getData();

        int offset = pack.getOffset();

        int sendSeq;

        if (data[offset++] != '$' || data[offset++] != PACKET_TYPE_FIND_DEVICE_REQ_10) {

            return false;

        }

        sendSeq = data[offset++] & 0xFF;

        sendSeq |= (data[offset++] << 8 );

        sendSeq |= (data[offset++] << 16);

        sendSeq |= (data[offset++] << 24);

        return sendSeq >= 1 && sendSeq <= 3;

    }


    /**

     * 校验确认数据

     * 协议:$ + packType(1) + sendSeq(4) + deviceIP(n<=15)

     *  packType - 报文类型

     *  sendSeq - 发送序列

     *  deviceIP - 设备IP,仅确认时携带

     */

    private boolean verifyCheckData(DatagramPacket pack) {

        if (pack.getLength() < 6) {

            return false;

        }


        byte[] data = pack.getData();

        int offset = pack.getOffset();

        int sendSeq;

        if (data[offset++] != '$' || data[offset++] != PACKET_TYPE_FIND_DEVICE_CHK_12) {

            return false;

        }

        sendSeq = data[offset++] & 0xFF;

        sendSeq |= (data[offset++] << 8 );

        sendSeq |= (data[offset++] << 16);

        sendSeq |= (data[offset++] << 24);

        if (sendSeq < 1 || sendSeq > RESPONSE_DEVICE_MAX) {

            return false;

        }


        String ip = new String(data, offset, pack.getLength() - offset, Charset.forName("UTF-8"));

        Log.i(TAG, "@@@zjun: ip from host=" + ip);

        return ip.equals(getOwnWifiIP());

    }


    /**

     * 获取本机在Wifi中的IP

     */

    private String getOwnWifiIP() {

        WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);

        if (!wm.isWifiEnabled()) {

            return "";

        }


        // 需加权限:android.permission.ACCESS_WIFI_STATE

        WifiInfo wifiInfo = wm.getConnectionInfo();

        int ipInt = wifiInfo.getIpAddress();

        String ipAddr = int2Ip(ipInt);

        Log.i(TAG, "@@@zjun: 本机IP=" + ipAddr);

        return int2Ip(ipInt);

    }


    /**

     * int表示的ip转换成字符串ip

     */

    private String int2Ip(int i) {

        return String.format("%d.%d.%d.%d", i & 0xFF, (i >> 8) & 0xFF, (i >> 16) & 0xFF, (i >> 24) & 0xFF);

    }

}



运行代码

遥控器端:

final List<SearchThread.DeviceBean> mDeviceList = new ArrayList<>();


   new SearchThread() {

       @Override

       public void onSearchStart() {

          Log.e("search_device","start search");

       }


       @Override

       public void onSearchFinish(Set deviceSet) {

           mDeviceList.clear();

           mDeviceList.addAll(deviceSet);

           for(SearchThread.DeviceBean deviceBean:mDeviceList){

               Log.e("search_device",deviceBean.ip);

           }

       }

   }.start();


电视盒子端开启监听:

new WaitingSearch(this, "日灯光", "客厅"){

    @Override

    public void onDeviceSearched(InetSocketAddress socketAddr) {

       // pushMsgToMain("已上线,搜索主机:" + socketAddr.getAddress().getHostAddress() + ":" + socketAddr.getPort());

        Log.e("find_deivce",socketAddr.getAddress().getHostAddress());

    }

}.start();



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值