接上面的UDP,本篇主要讨论如何在局域网中搜索所有的设备,这个需求在物联网应用的比较多,也比较综合,特把名字加在标题中了。最后一块是网络编程的常见问题。
假设你家里安装了智能家居,所有的设备都是通过Wifi连接自己家里的局域网(至于这些设备没有界面操作,如何连接wifi?有一个比较流行的牛逼技术,叫SmartConfig)。现在这些设备连入到局域网了,那如何通过Android搜索到这些设备?
模拟主机效果图:
模拟设备效果图:
这些设备在局域网内,肯定是通过DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)来获取内网IP的。也就是说每个设备的IP都不是固定的。而我们主要目的就是要获取这些设备的IP地址。
也许你说,把手机设置成一个固定的内网IP,然后让这些设备来连接这个固定IP。看上去OK啊,但万一这个IP被占用了,怎办?
每个设备的IP会变,但通信端口我们肯定可以固定。这就可以运用上面的UDP广播(或组播)技术。具体流程:
本解决方法有以下特点:
下面是广播实现的代码,当然也可以用组播来实现。组播因为组播地址的原因,可以进一步加强安全性,代码中把广播的网络那块改成组播就好了。(组播参考:Android网络编程TCP、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;/*** 设备搜索类* Created by zjun on 2016/9/3.*/
public abstract class DeviceSearcher extends Thread {private static final String TAG = DeviceSearcher.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
}
主机——demo核心代码:
private List
private void searchDevices_broadcast() {new DeviceSearcher() {@Overridepublic void onSearchStart() {startSearch(); // 主要用于在UI上展示正在搜索}@Overridepublic void onSearchFinish(Set deviceSet) {endSearch(); // 结束UI上的正在搜索mDeviceList.clear();mDeviceList.addAll(deviceSet);mHandler.sendEmptyMessage(0); // 在UI上更新设备列表}}.start();
}
设备——设备等待搜索类:
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;/*** 设备等待搜索类* Created by zjun on 2016/9/4.*/
public abstract class DeviceWaitingSearch extends Thread {private final String TAG &#61; DeviceWaitingSearch.class.getSimpleName();private static final int DEVICE_FIND_PORT &#61; 9000;private static final int RECEIVE_TIME_OUT &#61; 1500; // 接收超时时间&#xff0c;应小于等于主机的超时时间1500private static final int RESPONSE_DEVICE_MAX &#61; 200; // 响应设备的最大个数&#xff0c;防止UDP广播攻击private static final byte PACKET_TYPE_FIND_DEVICE_REQ_10 &#61; 0x10; // 搜索请求private static final byte PACKET_TYPE_FIND_DEVICE_RSP_11 &#61; 0x11; // 搜索响应private static final byte PACKET_TYPE_FIND_DEVICE_CHK_12 &#61; 0x12; // 搜索确认private static final byte PACKET_DATA_TYPE_DEVICE_NAME_20 &#61; 0x20;private static final byte PACKET_DATA_TYPE_DEVICE_ROOM_21 &#61; 0x21;private Context mContext;private String deviceName, deviceRoom;public DeviceWaitingSearch(Context context, String name, String room) {mContext &#61; context;deviceName &#61; name;deviceRoom &#61; room;}&#64;Overridepublic void run() {DatagramSocket socket &#61; null;try {socket &#61; new DatagramSocket(DEVICE_FIND_PORT);byte[] data &#61; new byte[1024];DatagramPacket pack &#61; new DatagramPacket(data, data.length);while (true) {// 等待主机的搜索socket.receive(pack);if (verifySearchData(pack)) {byte[] sendData &#61; packData();DatagramPacket sendPack &#61; new DatagramPacket(sendData, sendData.length, pack.getAddress(), pack.getPort());Log.i(TAG, "&#64;&#64;&#64;zjun: 给主机回复信息");socket.send(sendPack);Log.i(TAG, "&#64;&#64;&#64;zjun: 等待主机接收确认");socket.setSoTimeout(RECEIVE_TIME_OUT);try {socket.receive(pack);if (verifyCheckData(pack)) {Log.i(TAG, "&#64;&#64;&#64;zjun: 确认成功");onDeviceSearched((InetSocketAddress) pack.getSocketAddress());break;}} catch (SocketTimeoutException e) {}socket.setSoTimeout(0); // 连接超时还原成无穷大&#xff0c;阻塞式接收}}} catch (IOException e) {e.printStackTrace();} finally {if (socket !&#61; null) {socket.close();}}}/*** 当设备被发现时执行*/public abstract void onDeviceSearched(InetSocketAddress socketAddr);/*** 打包响应报文* 协议&#xff1a;$ &#43; packType(1) &#43; data(n)* data: 由n组数据&#xff0c;每组的组成结构type(1) &#43; length(4) &#43; data(length)* type类型中包含name、room类型&#xff0c;但name必须在最前面*/private byte[] packData() {byte[] data &#61; new byte[1024];int offset &#61; 0;data[offset&#43;&#43;] &#61; &#39;$&#39;;data[offset&#43;&#43;] &#61; PACKET_TYPE_FIND_DEVICE_RSP_11;byte[] temp &#61; getBytesFromType(PACKET_DATA_TYPE_DEVICE_NAME_20, deviceName);System.arraycopy(temp, 0, data, offset, temp.length);offset &#43;&#61; temp.length;temp &#61; getBytesFromType(PACKET_DATA_TYPE_DEVICE_ROOM_21, deviceRoom);System.arraycopy(temp, 0, data, offset, temp.length);offset &#43;&#61; temp.length;byte[] retVal &#61; new byte[offset];System.arraycopy(data, 0, retVal, 0, offset);return retVal;}private byte[] getBytesFromType(byte type, String val) {byte[] retVal &#61; new byte[0];if (val !&#61; null) {byte[] valBytes &#61; val.getBytes(Charset.forName("UTF-8"));retVal &#61; new byte[5 &#43; valBytes.length];retVal[0] &#61; type;retVal[1] &#61; (byte) valBytes.length;retVal[2] &#61; (byte) (valBytes.length >> 8 );retVal[3] &#61; (byte) (valBytes.length >> 16);retVal[4] &#61; (byte) (valBytes.length >> 24);System.arraycopy(valBytes, 0, retVal, 5, valBytes.length);}return retVal;}/*** 校验搜索数据* 协议&#xff1a;$ &#43; packType(1) &#43; sendSeq(4)* packType - 报文类型* sendSeq - 发送序列*/private boolean verifySearchData(DatagramPacket pack) {if (pack.getLength() !&#61; 6) {return false;}byte[] data &#61; pack.getData();int offset &#61; pack.getOffset();int sendSeq;if (data[offset&#43;&#43;] !&#61; &#39;$&#39; || data[offset&#43;&#43;] !&#61; PACKET_TYPE_FIND_DEVICE_REQ_10) {return false;}sendSeq &#61; data[offset&#43;&#43;] & 0xFF;sendSeq |&#61; (data[offset&#43;&#43;] <<8 );sendSeq |&#61; (data[offset&#43;&#43;] <<16);sendSeq |&#61; (data[offset&#43;&#43;] <<24);return sendSeq >&#61; 1 && sendSeq <&#61; 3;}/*** 校验确认数据* 协议&#xff1a;$ &#43; packType(1) &#43; sendSeq(4) &#43; deviceIP(n<&#61;15)* packType - 报文类型* sendSeq - 发送序列* deviceIP - 设备IP&#xff0c;仅确认时携带*/private boolean verifyCheckData(DatagramPacket pack) {if (pack.getLength() <6) {return false;}byte[] data &#61; pack.getData();int offset &#61; pack.getOffset();int sendSeq;if (data[offset&#43;&#43;] !&#61; &#39;$&#39; || data[offset&#43;&#43;] !&#61; PACKET_TYPE_FIND_DEVICE_CHK_12) {return false;}sendSeq &#61; data[offset&#43;&#43;] & 0xFF;sendSeq |&#61; (data[offset&#43;&#43;] <<8 );sendSeq |&#61; (data[offset&#43;&#43;] <<16);sendSeq |&#61; (data[offset&#43;&#43;] <<24);if (sendSeq <1 || sendSeq > RESPONSE_DEVICE_MAX) {return false;}String ip &#61; new String(data, offset, pack.getLength() - offset, Charset.forName("UTF-8"));Log.i(TAG, "&#64;&#64;&#64;zjun: ip from host&#61;" &#43; ip);return ip.equals(getOwnWifiIP());}/*** 获取本机在Wifi中的IP*/private String getOwnWifiIP() {WifiManager wm &#61; (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);if (!wm.isWifiEnabled()) {return "";}// 需加权限&#xff1a;android.permission.ACCESS_WIFI_STATEWifiInfo wifiInfo &#61; wm.getConnectionInfo();int ipInt &#61; wifiInfo.getIpAddress();String ipAddr &#61; int2Ip(ipInt);Log.i(TAG, "&#64;&#64;&#64;zjun: 本机IP&#61;" &#43; 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);}
}
设备——demo核心代码&#xff1a;
private void initData() {new DeviceWaitingSearch(this, "日灯光", "客厅"){&#64;Overridepublic void onDeviceSearched(InetSocketAddress socketAddr) {pushMsgToMain("已上线&#xff0c;搜索主机&#xff1a;" &#43; socketAddr.getAddress().getHostAddress() &#43; ":" &#43; socketAddr.getPort());}}.start();
}
因为用了电脑作为测试设备&#xff0c;包括Java工程和Android模拟器&#xff0c;之前就知道Java工程中要网络通信要关防火墙&#xff0c;但使用的时候&#xff0c;发现Android模拟器、C工程、和Socket网络工具都可以通信&#xff0c;就Java工程不行。
尝试了很多方法找原因&#xff0c;如在命令行执行下面的命令&#xff0c;然而无终而返&#xff1a;
最后终于找到解决办法&#xff0c;在“防火墙”的“允许的应用”中需要设置权限。把“Java(TM) Platform SE binary”勾上&#xff0c;并把后面的“专用”和“公用”网络都勾选上&#xff1a;
其实问题详细情况是这样的&#xff1a;无线Wifi连接的设备不能与无线设备通信&#xff08;内网IP通信&#xff09;&#xff0c;只能与有线设备通信&#xff1b;而有线设备一切正常。
这问题也很郁闷&#xff0c;查了资料也没有找到解决办法。但个人感觉这问题肯定是路由器的问题&#xff0c;因为局域网的控制系统就是路由器。幸运的是&#xff0c;我有两个一模一样的路由器&#xff0c;另一个路由器应该的。然后两台路由器&#xff0c;分别连两台电脑&#xff0c;通过电脑对路由器界面进行对比&#xff08;英文是硬伤啊⊙﹏⊙b&#xff09;。
最后锁定了这个东西“Wireless Isolation within SSID”&#xff0c;就是连接SSID的设备都独立&#xff0c;无法进行局域网内通信。曾经手滑了一下&#xff0c;点成Enable。改回Disabled&#xff0c;兄弟间就别分开了&#xff1a;
本来一篇想搞定的&#xff0c;结果写了三篇&#xff0c;目录还是按原来一篇的来写&#xff0c;有点儿乱(^__^) ……
网络编程&#xff0c;终于赶在这个周末结束前告一段落。