标签:trace smt udp报文 物理层 地址解析 指定 机器 NPU 开发
一般情况下,IOT设备(针对wifi设备)在智能化过程中需要连接到家庭路由。但在此之前,需要将wifi信息(通常是ssid和password,即名字和密码)发给设备,这一步骤被称为配网。移动设备如Android、iOS等扮演发送wifi信息的角色,简单来说就是移动应用要与IOT设备建立通信,进而交换数据。针对配网这一步骤,市面上一般有两种做法:
可以发现,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地址分类如下:
IP地址是每一台主机唯一的标识符,由网络号和主机号组成。A、B、C三类均为单播地址(一对一),D类为多播地址(一对多)。
在两级中新增了子网号字段,也称为划分子网。其方法是从主机号借用若干位作为子网号。
子网掩码:是一个网络或子网的重要属性,可通过子网掩码计算出目的主机所处于哪一个子网。若没有划分子网,则使用默认子网掩码(A类255.0.0.0、B类255.255.0.0、C类255.255.255.0)
无分类编址(CIDR)也称为构造超网,使用网络前缀+主机号规则,并使用斜线标明前缀位数,如:
128.15.34.77/20
多播又称为组播,提供一对多的通信,大大节约网络资源。IP数据报中不能写入某一个IP地址,需写入多播组的标识符(需要接收的主机与此标识符关联)。D类地址即为多播组的标识符,所以多播地址(D类)只能作为目的地址。分为本局域网上的硬件多播、互联网多播两种。
多播使用到的协议:
运输层向上面的应用层提供通信服务,通信的端点并不是主机而是主机中进程,使用协议端口号标识进程(如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,连接服务器,进而绑定账号。
标签:trace smt udp报文 物理层 地址解析 指定 机器 NPU 开发
原文地址:https://www.cnblogs.com/haybl/p/13195595.html