Android下实现局域网设备发现与通信

Android下实现局域网设备发现与通信

在使用Android开发智能设备时,一般会分为用于遥控与管理的Host端,和用于执行个性功能的Slave端,二者可以借助网络或蓝牙等途径实现通信。

局域网设备发现

如果是借助网络通信,那就必须知道对方的ip地址,而常见的网络环境中ip地址一般是通过DHCP服务动态分配的,所以事先无法确定对方的ip地址。为了确定对方的地址,可以通过向局域网内发送查找设备的广播,收到广播的Slave端就知道了Host端的ip地址,在向Host端发送应答包之后,双方就都知道了对方的ip地址。


局域网设备通信

在Host端与Slave端互相知道ip地址后,就可以实现局域网通信了。局域网通信一般通过TCP或UDP实现,TCP的优势在于它的可靠性,通过TCP传送的数据不丢失、无差错、不重复且按序到达,但只支持一对一,效率比UDP低;UDP的优势在于效率比TCP高,支持一对一、一对多、多对一、多对多,但不保证可靠性,网络差的环境下可能丢包和顺序错乱。因此如果对可靠性要求较高,建议使用TCP,如果对实时性要求较高则可以使用UDP。


代码实现设备发现

编写用于搜索局域网中设备DeviceSearcher,向局域网发送特定格式的广播包,并接收应答包,从而发现局域网中的己方设备。

/**
 * 用于搜索局域网中的设备
 */
public class DeviceSearcher {

	private static ExecutorService executorService = Executors.newSingleThreadExecutor();
	private static Handler uiHandler = new Handler(Looper.getMainLooper());

	/**
	 * 开始搜索
	 * @param onSearchListener
	 */
	public static void search(OnSearchListener onSearchListener){
		executorService.execute(new SearchRunnable(onSearchListener));
	}
	
	public static interface OnSearchListener{
		void onSearchStart();
		void onSearchedNewOne(Device device);
		void onSearchFinish();
	}
	
	private static class SearchRunnable implements Runnable {
		
		OnSearchListener searchListener;
		
		public SearchRunnable(OnSearchListener listener){
			this.searchListener = listener;
		}
		
		@Override
		public void run() {
			try {
				if(searchListener!=null){
					uiHandler.post(new Runnable() {
						@Override
						public void run() {
							searchListener.onSearchStart();
						}
					});
				}
				DatagramSocket socket = new DatagramSocket();
				//设置接收等待时长
				socket.setSoTimeout(RemoteConst.RECEIVE_TIME_OUT);
				byte[] sendData = new byte[1024];
				byte[] receData = new byte[1024];
                DatagramPacket recePack = new DatagramPacket(receData, receData.length);
                //使用广播形式(目标地址设为255.255.255.255)的udp数据包
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("255.255.255.255"), RemoteConst.DEVICE_SEARCH_PORT);
                //用于存放已经应答的设备
                HashMap<String, Device> devices = new HashMap<>();
                //搜索指定次数
				for(int i=0;i<RemoteConst.SEARCH_DEVICE_TIMES;i++){
					sendPacket.setData(packSearchData(i+1));
					//发送udp数据包
					socket.send(sendPacket);
	                try {
	                	//限定搜索设备的最大数量
	                    int rspCount = RemoteConst.SEARCH_DEVICE_MAX;
	                    while (rspCount > 0) {
	                        socket.receive(recePack);
	                        final Device device = parseRespData(recePack);
	                        if(devices.get(device.getIp())==null){
	                        	//保存新应答的设备
		                        devices.put(device.getIp(), device);
								if(searchListener!=null){
									uiHandler.post(new Runnable() {
										@Override
										public void run() {
											searchListener.onSearchedNewOne(device);
										}
									});
								}
	                        }
							rspCount --;
	                    }
	                } catch (SocketTimeoutException e) {
	                	e.printStackTrace();
	                }
				}
				socket.close();
				if(searchListener!=null){
					uiHandler.post(new Runnable() {
						@Override
						public void run() {
							searchListener.onSearchFinish();
						}
					});
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

        /**
         * 校验和解析应答的数据包
		 * @param pack udp数据包
		 * @return
         */
		private Device parseRespData(DatagramPacket pack) {
			if (pack.getLength() < 2) {
	            return null;
	        }
	        byte[] data = pack.getData();
	        int offset = pack.getOffset();
	        //检验数据包格式是否符合要求
	        if (data[offset++] != RemoteConst.PACKET_PREFIX || data[offset++] != RemoteConst.PACKET_TYPE_SEARCH_DEVICE_RSP) {
	            return null;
	        }
	        int length = data[offset++];
        	String uuid = new String(data, offset, length);
			return new Device(pack.getAddress().getHostAddress(), pack.getPort(), uuid);
		}

		/**
         * 生成搜索数据包
		 * 格式:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]
		 *  packType - 报文类型
		 *  sendSeq - 发送序列
		 *  dataLen - 数据长度
		 *  data - 数据内容
		 * @param seq
         * @return
         */
		private byte[] packSearchData(int seq) {
			byte[] data = new byte[6];
			int offset = 0;
			data[offset++] = RemoteConst.PACKET_PREFIX;
			data[offset++] = RemoteConst.PACKET_TYPE_SEARCH_DEVICE_REQ;
			data[offset++] = (byte) seq;
			data[offset++] = (byte) (seq >> 8);
			data[offset++] = (byte) (seq >> 16);
			data[offset++] = (byte) (seq >> 24);
			return data;
		}
	}


}

编写用于响应搜索广播的DeviceSearchResponser,监听局域网中的广播包,在收到符合要求的广播包后发送应答包,并带上自己的标识。

/**
 * 用于响应局域网设备搜索
 */
public class DeviceSearchResponser {

    private static SearchRespThread searchRespThread;

    /**
     * 启动响应线程,收到设备搜索命令后,自动响应
     */
    public static void open() {
        if (searchRespThread == null) {
            searchRespThread = new SearchRespThread();
            searchRespThread.start();
        }
    }

    /**
     * 停止响应
     */
    public static void close() {
        if (searchRespThread != null) {
            searchRespThread.destory();
            searchRespThread = null;
        }
    }

    private static class SearchRespThread extends Thread {

        DatagramSocket socket;
        volatile boolean openFlag;

        public void destory() {
            if (socket != null) {
                socket.close();
                socket = null;
            }
            openFlag = false;
        }

        @Override
        public void run() {
            try {
                //指定接收数据包的端口
                socket = new DatagramSocket(RemoteConst.DEVICE_SEARCH_PORT);
                byte[] buf = new byte[1024];
                DatagramPacket recePacket = new DatagramPacket(buf, buf.length);
                openFlag = true;
                while (openFlag) {
                    socket.receive(recePacket);
                    //校验数据包是否是搜索包
                    if (verifySearchData(recePacket)) {
                        //发送搜索应答包
                        byte[] sendData = packSearchRespData();
                        DatagramPacket sendPack = new DatagramPacket(sendData, sendData.length, recePacket.getSocketAddress());
                        socket.send(sendPack);
                    }
                }
            } catch (IOException e) {
                destory();
            }
        }

        /**
         * 生成搜索应答数据
         * 协议:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]
         * packType - 报文类型
         * sendSeq - 发送序列
         * dataLen - 数据长度
         * data - 数据内容
         * @return
         */
        private byte[] packSearchRespData() {
            byte[] data = new byte[1024];
            int offset = 0;
            data[offset++] = RemoteConst.PACKET_PREFIX;
            data[offset++] = RemoteConst.PACKET_TYPE_SEARCH_DEVICE_RSP;

            // 添加UUID数据
            byte[] uuid = getUuidData();
            data[offset++] = (byte) uuid.length;
            System.arraycopy(uuid, 0, data, offset, uuid.length);
            offset += uuid.length;
            byte[] retVal = new byte[offset];
            System.arraycopy(data, 0, retVal, 0, offset);
            return retVal;
        }

        /**
         * 校验搜索数据是否符合协议规范
         * 协议:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]
         * packType - 报文类型
         * sendSeq - 发送序列
         * dataLen - 数据长度
         * data - 数据内容
         */
        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++] != RemoteConst.PACKET_TYPE_SEARCH_DEVICE_REQ) {
                return false;
            }
            sendSeq = data[offset++] & 0xFF;
            sendSeq |= (data[offset++] << 8) & 0xFF00;
            sendSeq |= (data[offset++] << 16) & 0xFF0000;
            sendSeq |= (data[offset++] << 24) & 0xFF000000;
            if (sendSeq < 1 || sendSeq > RemoteConst.SEARCH_DEVICE_TIMES) {
                return false;
            }

            return true;
        }

        /**
         * 获取设备uuid
         * @return
         */
        private byte[] getUuidData() {
            return (Build.PRODUCT + Build.ID).getBytes();
        }
    }
}


代码实现设备通信

编写用于向Slave端发送命令的CommandSender

	/**
     * 用于发送命令
     * Created by gw on 2017/11/4.
     */
    public class CommandSender {
        private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new SendCommandThreadFactory(), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                throw new RejectedExecutionException();
            }
        });


        public static void addCommand(final Command command){
            addTask(new CommandRunnable(command));
        }

        private static void addTask(CommandRunnable runnable){
            try{
                threadPool.execute(runnable);
            }catch (RejectedExecutionException e){
                e.printStackTrace();
                if(runnable.command.getCallback()!=null){
                    runnable.command.getCallback().onError("command is rejected");
                }
            }
        }

        private static class CommandRunnable implements Runnable{

            Command command;

            public CommandRunnable(Command command){
                this.command = command;
            }

            @Override
            public void run() {
                Socket socket = new Socket();
                try {
                    socket.connect(new InetSocketAddress(command.getDestIp(), RemoteConst.COMMAND_RECEIVE_PORT));
                    OutputStream os = socket.getOutputStream();
                    InputStream is = socket.getInputStream();
                    byte[] buffer = new byte[1024*8];
                    //发送命令内容
                    os.write(command.getContent().getBytes());
                    os.write(CommunicationKey.EOF.getBytes());
                    if(command.getCallback()!=null){
                        command.getCallback().onRequest(command.getContent());
                    }
                    //读取应答内容
                    int i=0;
                    while (true) {
                        buffer[i] = (byte) is.read();
                        if(buffer[i] == -1){
                            if(command.getCallback()!=null){
                                command.getCallback().onError("get response failed");
                            }
                            break;
                        }
                        if((char)buffer[i] != CommunicationKey.EOF.charAt(0)){
                            i++;
                        }else{
                            String response = new String(buffer, 0, i+1, Charset.defaultCharset()).replace(CommunicationKey.EOF, "");
                            if (response.startsWith(CommunicationKey.RESPONSE_OK)) {
                                if(command.getCallback()!=null){
                                    command.getCallback().onSuccess(response.replace(CommunicationKey.RESPONSE_OK, ""));
                                }
                                break;
                            }else if (response.startsWith(CommunicationKey.RESPONSE_ECHO)) {
                                if(command.getCallback()!=null){
                                    command.getCallback().onEcho(response.replace(CommunicationKey.RESPONSE_ECHO, ""));
                                }
                                break;
                            }else if (response.startsWith(CommunicationKey.RESPONSE_ERROR)) {
                                if(command.getCallback()!=null){
                                    command.getCallback().onError(response.replace(CommunicationKey.RESPONSE_ERROR, ""));
                                }
                                break;
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    if(command.getCallback()!=null){
                        command.getCallback().onError(e.getMessage());
                    }
                }finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

编写用于接受Host端命令并回写应答的CommandReceiver

/**
 * 用于接收命令和回写应答
 * Created by gw on 2017/11/6.
 */
public class CommandReceiver {

    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(7, 8, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ReceiveCommandThreadFactory(), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            throw new RejectedExecutionException();
        }
    });
    private static CommandListener listener;
    private static volatile boolean isOpen;


    public static void open(CommandListener commandListener){
        listener = commandListener;
        isOpen = true;
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(RemoteConst.COMMAND_RECEIVE_PORT);
                    while(isOpen){
                        Socket socket = serverSocket.accept();
                        threadPool.execute(new CommandParseRunnable(socket));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public static void close(){
        isOpen = false;
        threadPool.shutdown();
    }

    public static class CommandParseRunnable implements Runnable{
        Socket socket;

        public CommandParseRunnable(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                DataInputStream is = new DataInputStream(socket.getInputStream());
                OutputStream os = socket.getOutputStream();
                byte[] bytes = new byte[1024*8];
                int i=0;
                while(true){
                    bytes[i] = (byte) is.read();
                    if (bytes[i] == -1) {
                        break;
                    }
                    if((char)bytes[i] != CommunicationKey.EOF.charAt(0)){
                        i++;
                    }else{
                        String command = new String(bytes, 0, i+1, Charset.defaultCharset()).replace(CommunicationKey.EOF, "");
                        if(listener!=null){
                            os.write((listener.onReceive(command)).getBytes());
                        }else{
                            os.write((CommunicationKey.RESPONSE_OK+ CommunicationKey.EOF).getBytes());
                        }
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(socket != null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public interface CommandListener{
        String onReceive(String command);
    }
}


Demo项目下载地址

https://github.com/wei-gong/LAN_Search_Communication

  • 5
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值