通常狀況下,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,鏈接服務器,進而綁定帳號。