官方示例说明
/*
创建了一个WiFi接入点,并在其上提供了一个web服务器。
步骤:
1. 连接到接入点“yourAp”
2. 将您的web浏览器指向http://192.168.4.1/H打开LED或http://192.168.4.1/L关闭它
或
在PuTTY终端上以192.168.4.1为IP地址,80为端口,运行raw TCP“GET /H”和“GET /L”
*/
#include <WiFi.h>
#include <WiFiAP.h>
#define LED_BUILTIN 12 // 设置连接测试LED的GPIO引脚,如果开发板有内置LED,则注释此行
//设置这些为您想要的凭据
const char *ssid = "yourAP";
const char *password = "yourPassword";
IPAddress apIP(192, 168, 4, 1); //设置AP的IP地址
WiFiServer server(80);
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
Serial.println();
Serial.println("Configuring access point...");
//如果要开放AP,可以去掉password参数。
//密码长度必须大于7位
if (!WiFi.softAP(ssid, password)) {
log_e("Soft AP creation failed.");
while(1);
}
WiFi.softAPConfig( apIP, apIP, IPAddress(255, 255, 255, 0));
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
server.begin();
Serial.println("Server started");
}
void loop() {
WiFiClient client = server.available(); // 监听接入的客户端
if (client) { // 如果接入一个客户端,
Serial.println("New Client."); // 则在串口打印一条提示信息
String currentLine = ""; // 创建一个字符串来保存来自客户端的传入数据
while (client.connected()) { // 在客户端连接时循环
if (client.available()) { // 如果要从客户端读取字节,
char c = client.read(); // 读取一个字节,然后
Serial.write(c); // 在串行监视器上打印出来
if (c == '\n') { // 如果字节是换行字符
// 如果当前行为空,则一行中有两个换行符。
// 这是客户端HTTP请求的结束,所以发送一个响应:
if (currentLine.length() == 0) {
// HTTP报头总是以状态行开头(例如HTTP/1.1 200 OK)
// 还有一个content-type,这样客户端就知道接下来会发生什么,然后是一个空行:
client.println("HTTP/1.1 200 OK"); //状态行
client.println("Content-type:text/html"); //响应头
client.println();
// HTTP响应的内容在报头后面: //响应体
client.print("Click <a href=\"/H\">here</a> to turn ON the LED.<br>");
client.print("Click <a href=\"/L\">here</a> to turn OFF the LED.<br>");
// HTTP响应以另一个空行结束:
client.println();
// 跳出while循环:
break;
} else { // 如果你有一个换行符,那么清除currentLine:
currentLine = "";
}
} else if (c != '\r') { // 如果你有除了回车符以外的任何字符,
currentLine += c; // 将它添加到currentLine的末尾
}
// 检查客户端请求是“GET /H”还是“GET /L”:
if (currentLine.endsWith("GET /H")) {
digitalWrite(LED_BUILTIN, HIGH); // GET /H打开LED
}
if (currentLine.endsWith("GET /L")) {
digitalWrite(LED_BUILTIN, LOW); // GET /L关闭LED
}
}
}
// 关闭连接:
client.stop();
Serial.println("Client Disconnected.");
}
}
代码实现的目标与实现过程
目标:
创建了一个WiFi接入点,并在其上提供了一个web服务器。
用户操作:
1. 连接到接入点“yourAp”
2. 将您的web浏览器指向http://192.168.4.1/H打开LED或http://192.168.4.1/L关闭它或在PuTTY终端上以192.168.4.1为IP地址,80为端口,运行raw TCP“GET /H”和“GET /L”
过程:
-
创建一个热点,设置esp地址为192.168.4.1,端口80
-
创建服务器
-
等待客户端接入
-
客户端接入后,循环遍历客户端是否发来消息
-
若发来消息,则读取信息,并将每次读取到的信息保存到字符变量‘c’中
以下为重点,请务必仔细阅读
-
只要‘c’接收到的信息不是空行,则说明esp服务器还没有完整收到客户端的http请求。因此将每次收到的数据加入到字符串变量currentLine的末尾,并检查是否以“GET /H”还是“GET /L”为结尾,若对得上则执行相应的控制动作。
这与HTTP请求的格式和协议有关。在常见的HTTP协议中,HTTP请求由三部分组成:请求行、请求头和请求体。
请求行中包含了请求的方法(例如GET、POST)、路径(URL)和协议版本(例如HTTP/1.1)。
请求头包含了额外的元数据信息,如请求的主机、内容类型、Accept-Encoding等。
请求体包含了可选的请求数据,主要用于POST请求发送数据给服务器。
在这段代码中,我们主要关注了请求行部分。当一个HTTP请求结束时,会有两个连续的换行符(
\n\n
)出现,表示请求行之后没有额外的请求头和请求体的内容。因此,如果当前行为空(即
currentLine.length() == 0
),那么我们可以认为这是一个空行,表示请求的头部结束,没有请求体,因此可以断定整个请求结束了。这时服务器可以发送对应的HTTP响应。需要注意的是,这段代码并没有处理请求头和请求体的内容,它只关注了请求的路径部分,根据不同的路径进行LED的控制。
以下为这段代码收到的一个http请求示例
GET /H HTTP/1.1
Host: 192.168.4.1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 11; RMX3161 Build/RKQ1.201217.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/12.16 SearchCraft/3.9.1 (Baidu; P1 11)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
dnt: 1
X-Requested-With: mark.via
-
判断http请求接收完成后,需要对这个请求进行回应,按照标准的http响应格式,
一个HTTP响应由三部分组成:状态行、响应头和响应体。
状态行包含了响应的状态码和对应的状态消息,例如
HTTP/1.1 200 OK
表示请求成功。响应头包含了额外的元数据信息,如响应的内容类型、长度、缓存控制等。
响应体包含了实际的响应数据,如HTML内容、文件等。
在Arduino的代码中,
client.println("xxx")
这一行是用来发送响应体数据的,而在这个特定的代码中,因为响应体为空,所以只发送了一个空行。根据HTTP协议的规范,一个HTTP响应以两个连续的换行符来表示响应头的结束,也就是空行。这是因为响应头和响应体之间需要有一个空行来分隔。
因此,在这个代码片段中,
client.println()
这一行的作用是发送一个空行,表示响应头的结束,然后服务器不再发送其他响应体内容。这样就完成了一个空的HTTP响应。这个空行的目的是满足HTTP协议中响应的格式要求,确保正确解析和处理响应。
对用到的一些函数和类进行说明:
WiFi.softAP(ssid, password)
WiFi.softAPIP()
WiFiServer server(80)
创建侦听指定端口上的传入连接的服务器。
参数:端口:要侦听的端口 (INT)
无返回值
server.begin()
告知服务器开始侦听传入连接
无参无返回值
server.available()
无参
返回值:客户端对象; 如果没有客户端有可供读取的数据,则此对象将在 if 语句中计算为 false
server.write()
将数据写入连接到服务器的所有客户端。
参数:
数据:要写入的值(字节或字符)
返回:
字节 :写入的字节数。没有必要阅读此内容。
WiFiClient client
创建一个客户端,该客户端可以连接到 client.connect() 中定义的指定互联网 IP 地址和端口。
无参无返回值
client.connected()
客户端是否已连接。请注意,如果连接已关闭但仍有未读数据,则认为客户端已连接。
无参
返回值:如果客户端已连接,则返回 true,如果未连接,则返回 false。
client.available()
返回可供读取的字节数(即,它所连接的服务器已写入客户端的数据量)。
available() 继承自 Stream 实用程序类。
无参
返回值:可用字节数。
client.read()
读取从客户端连接到的服务器收到的下一个字节(在最后一次调用 read() 之后)。
无参
返回值:下一个字节(或字符),如果没有可用,则为 -1。
client.println()
将数据打印到客户端连接到的服务器,后跟回车符和换行符。将数字打印为数字序列,每个数字都是 ASCII 字符(例如,数字 123 作为三个字符“1”、“2”、“3”发送)。
参数:
数据(可选):要打印的数据(字符、字节、整数、长整型或字符串)
BASE(可选):用于打印数字的基数:DEC 表示十进制(以 10 为基数),OCT 表示八进制(以 8 为基数),十六进制表示十六进制(以 16 为基数)。
返回值:
byte:返回写入的字节数,尽管读取该数字是可选的
client.print()
将数据打印到客户端连接到的服务器。将数字打印为数字序列,每个数字都是 ASCII 字符(例如,数字 123 作为三个字符“1”、“2”、“3”发送)。
参数:
数据:要打印的数据(字符、字节、整数、长整型或字符串)
BASE(可选):用于打印数字的基数:,DEC 表示十进制(以 10 为基数),OCT 表示八进制(以 8 为基数),十六进制表示十六进制(以 16 为基数)。
返回值:
字节 :返回写入的字节数,尽管读取该数字是可选的
client.stop()
断开与服务器的连接
无参无返回值