通常状况下,IOT设备(针对wifi设备)在智能化过程当中须要链接到家庭路由。但在此以前,须要将wifi信息(一般是ssid和password,即名字和密码)发给设备,这一步骤被称为配网。移动设备如Android、iOS等扮演发送wifi信息的角色,简单来讲就是移动应用要与IOT设备创建通讯,进而交换数据。针对配网这一步骤,市面上通常有两种作法:java
能够发现,SmartConfig不需创建链接,步骤较少,实现起来也较容易,而且用户也无需进行过多的操做。本文的IOT设备基于ESP32开发板,解释原理及实现如何经过Android APP发出UDP
包实现SmartConfig。知识点:计算机网络、UDP
、组播、DatagramSocket
...编程
计算机网络分层结构以下:
服务器
HTTP
、DNS
、SMTP
。其交互的数据单元称为报文。IP(Internet Protocol)协议是网络层的主要协议。其版本有IPv4(32位)、IPv6(128位)。与IP协议配套使用的还有地址解析协议(ARP)、网际控制报文协议(ICMP,重要应用即常见的PING,测试连通性)、网际组管理协议(IGMP)。网络
IP数据报格式,由首部和数据部分两部分组成:
IP地址分类以下:socket
IP地址是每一台主机惟一的标识符,由网络号和主机号组成。A、B、C三类均为单播地址(一对一),D类为多播地址(一对多)。
ide
在两级中新增了子网号字段,也称为划分子网。其方法是从主机号借用若干位做为子网号。测试
子网掩码:是一个网络或子网的重要属性,可经过子网掩码计算出目的主机所处于哪个子网。若没有划分子网,则使用默认子网掩码(A类255.0.0.0、B类255.255.0.0、C类255.255.255.0)ui
无分类编址(CIDR)也称为构造超网,使用网络前缀+主机号规则,并使用斜线标明前缀位数,如:计算机网络
128.15.34.77/20
多播又称为组播,提供一对多的通讯,大大节约网络资源。IP数据报中不能写入某一个IP地址,需写入多播组的标识符(须要接收的主机与此标识符关联)。D类地址即为多播组的标识符,因此多播地址(D类)只能做为目的地址。分为本局域网上的硬件多播、互联网多播两种。3d
多播使用到的协议:
运输层向上面的应用层提供通讯服务,通讯的端点并非主机而是主机中进程,使用协议端口号标识进程(如HTTP为80)。UDP协议是运输层中重要的两个协议之一。
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操做抽象为几个简单的接口供应用层调用已实现进程在网络中通讯。简单来讲,socket是一种接口,对传输层(TCP/UPD协议)进行了的封装。
socket通讯:
Java为Socket编程封装了几个重要的类(均为客户端-服务端模式):
Socket
类:
实现了一个客户端socket,做为两台机器通讯的终端,默认采用TCP。connect()
方法请求socket链接、getXXXStream()
方法获取输入/出流、close()
关闭流。
ServerSocket
类:
实现了一个服务器的socket,等待客户端的链接请求。bind()
方法绑定一个IP
地址和端口、accept()
方法监听并返回一个Socket
对象(会阻塞)、close()
关闭一个socket
。
SocketAddress # InetSocketAddress
类:
前者是一个抽象类,提供了一个socket地址,不关心传输层协议;后者继承自前者,表示带有IP地址和端口号的socket地址。
DatagramSocket
类:
实现了一个发送和接收数据报的socket,使用UDP。send()
方法发送一个数据报(DatagramPacket
)、receive()
方法接收一个数据报(一直阻塞接至收到数据报或超时)、close()
方法关闭一个socket。
DatagramPacket
类:
使用DatagramSocket
时的数据报载体。
SmartConfig采用UDP实现,因此在前述知识的基础下,先编写一个例子熟悉java udp的使用,首先创建服务端的代码:
public class UDPServer { /** * 设置缓冲区的长度 */ private static final int BUFFER_SIZE = 255; /** * 指定端口,客户端需保持一致 */ private static final int PORT = 8089; public static void main(String[] args) { DatagramSocket datagramSocket = null; try { datagramSocket = new DatagramSocket(PORT); DatagramPacket datagramPacket = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE); while (true) { // 接收数据报,处于阻塞状态 datagramSocket.receive(datagramPacket); System.out.println("Receive data from client:" + new String(datagramPacket.getData())); // 服务器端发出响应信息 byte[] responseData = "Server response".getBytes(); datagramPacket.setData(responseData); datagramSocket.send(datagramPacket); } } catch (IOException e) { e.printStackTrace(); } finally { if (datagramSocket != null) { datagramSocket.close(); } } } }
客户端发出数据报:
public class UDPClient { /** * 指定端口,与服务端保持一致 */ private static final int PORT = 8089; /** * 超时重发时间 */ private static final int TIME_OUT = 2000; /** * 最大重试次数 */ private static final int MAX_RETRY = 3; public static void main(String[] args) throws IOException { try { byte[] sendMsg = "Client msg".getBytes(); // 建立数据报 DatagramSocket socket = new DatagramSocket(); // 设置阻塞超时时间 socket.setSoTimeout(TIME_OUT); // 建立server主机的ip地址(此处使用了本机地址) InetAddress inetAddress = InetAddress.getByName("192.168.xxx.xxx"); // 发送和接收的数据报文 DatagramPacket sendPacket = new DatagramPacket(sendMsg, sendMsg.length, inetAddress, PORT); DatagramPacket receivePacket = new DatagramPacket(new byte[sendMsg.length], sendMsg.length); // 数据报文可能丢失,设置重试计数器 int tryTimes = 0; boolean receiveResponse = false; // 将数据报文发送出去 socket.send(sendPacket); while (!receiveResponse && (tryTimes < MAX_RETRY)) { try { // 阻塞接收数据报文 socket.receive(receivePacket); // 检查返回的数据报文 if (!receivePacket.getAddress().equals(inetAddress)) { throw new IOException("Unknown server's data"); } receiveResponse = true; } catch (InterruptedIOException e) { // 重试 tryTimes++; System.out.println("TimeOut, try " + (MAX_RETRY - tryTimes) + " times"); } } if (receiveResponse) { System.out.println("Receive from server:" + new String(receivePacket.getData())); } else { System.out.println("No data!"); } socket.close(); } catch (SocketException e) { e.printStackTrace(); } } }
运行结果:
* 发现客户端收到的数据被截断了,这是由于没有重置接收包的长度,在服务端datagramPacket.setLength()
可解决。
根据前面的socket相关应用,基本想到如何实现一键配置。在实际应用中,原理同样,只是增长了组播(这一点须要和IOT设备端共同肯定,数据的格式也需协定)。在实现中,须要针对不一样IP组播地址发出循环的UDP报文,增长设备端接收到的可能性;同时APP也要开启服务端程序监听发出数据报的响应,以此更新UI或进行下一步的数据通讯。相关核心代码以下:
// 对每个组播地址循环发出报文 while (!mIsInterrupt && System.currentTimeMillis() - currentTime < mParameter .getTimeoutGuideCodeMillisecond()) { mSocketClient.sendData(gcBytes2, mParameter.getTargetHostname(), mParameter.getTargetPort(), mParameter.getIntervalGuideCodeMillisecond()); // 跳出条件,发出UDP报文达到必定时间 if (System.currentTimeMillis() - startTime > mParameter.getWaitUdpSendingMillisecond()) { break; } }
组播地址设置:
public String getTargetHostname() { if (mBroadcast) { return "255.255.255.255"; } else { int count = __getNextDatagramCount(); return "234." + (count + 1) + "." + count + "." + count; } }
完整代码省略(利益相关,代码匿了^_^
),基本思路很简单。最终的实现是IOT设备收到UDP发出的wifi信息,并以此成功链接wifi,链接服务器,进而绑定帐号。