2. WebSocket 服务器基础:创建 ESP32 WebSocket 服务器
好,咱们正式开始动手了。这一节,我们要在 ESP32 上搭建一个 WebSocket 服务器。说白了,就是让 ESP32 变成一个能实时跟浏览器“聊天”的小服务端。
我刚开始做物联网项目时,用的还是传统的 HTTP 轮询——每隔几秒发一次请求,又慢又费电。后来换成 WebSocket,那感觉,就像从拨号上网换成了光纤。你想想看,浏览器和 ESP32 之间能随时“推”数据,这才是实时控制该有的样子。
2.1 什么是 WebSocket 服务器
WebSocket 服务器,本质上就是一个常驻在 ESP32 上的网络服务。它监听某个端口(通常是 80 或 8080),等待客户端连接。
跟普通 HTTP 服务器最大的区别在于:
- HTTP 服务器:客户端请求一次,服务器响应一次,完事就断开。像你去柜台办业务,办完就走。
- WebSocket 服务器:客户端连上来后,连接一直保持。双方随时可以发消息。像你俩拉了个微信群,随时说话。
核心概念:WebSocket 服务器维护的是“长连接”。连接不断,通信就是实时的。
在 ESP32 上,我们通常用 ArduinoWebsockets 这个库来实现。它封装了底层细节,我们只需要关心三个事情:
- 服务器怎么启动
- 客户端连上来/断开时怎么办
- 收到消息后怎么处理
2.2 创建 WebSocket 服务器的步骤
嗯,这里要注意。创建服务器不是一上来就写代码,得先搭好“地基”。
我个人习惯把流程分成四步:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 引入库 | 包含 ArduinoWebsockets.h 和 WiFi 相关头文件 |
| 2 | 连接 WiFi | ESP32 必须先联网,才能提供 WebSocket 服务 |
| 3 | 创建服务器对象 | 实例化 WebsocketsServer,指定端口 |
| 4 | 启动并监听 | 调用 begin(),然后在 loop() 里轮询客户端事件 |
你看,其实不复杂。但我在项目中遇到过一个问题:很多人把 WiFi 连接和 WebSocket 服务器启动写在一起,结果 WiFi 没连上,服务器也启动失败。我的建议是——先确保 WiFi 连上了,再启动 WebSocket 服务器,分开处理,出错了也好排查。
2.3 处理客户端连接与断开
服务器启动后,最重要的就是处理客户端的“来”和“走”。
在 ArduinoWebsockets 库里,我们可以注册回调函数:
onClientConnect():当有浏览器或 App 连上来时触发onClientDisconnect():当客户端断开时触发
我曾经在一个智能家居项目里,没处理好断开事件,结果客户端掉线后服务器还在拼命发消息,导致内存泄漏。嗯,从那以后,我每次都会在断开回调里做清理工作。
小技巧:在 onClientConnect 里,可以打印客户端的 IP 地址,方便调试时知道是谁连上来了。
2.4 实现消息收发回调函数
消息收发是 WebSocket 的核心。服务器收到客户端发来的消息后,需要解析并做出响应。
回调函数的结构一般是这样的:
void onMessageCallback(WebsocketsClient client, WebsocketsMessage message) {
// 获取消息内容
String msg = message.data();
// 根据消息内容做不同处理
if (msg == "LED_ON") {
digitalWrite(LED_PIN, HIGH);
client.send("LED is now ON");
} else if (msg == "LED_OFF") {
digitalWrite(LED_PIN, LOW);
client.send("LED is now OFF");
} else {
client.send("Unknown command");
}
}
这里有几个关键点:
message.data()拿到的是字符串,你可以直接比较client.send()是向那个特定的客户端回复消息- 如果想广播给所有客户端,需要遍历客户端列表
注意:不要在回调函数里做耗时操作,比如 delay() 或者复杂的计算。WebSocket 回调是在主循环里触发的,阻塞太久会导致其他客户端连不上。
2.5 完整代码框架
下面是一个完整的 WebSocket 服务器框架。你可以直接复制到 Arduino IDE 里用:
#include <WiFi.h>
#include <ArduinoWebsockets.h>
using namespace websockets;
// WiFi 配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// LED 引脚
const int ledPin = 2;
// 创建 WebSocket 服务器
WebsocketsServer server;
WebsocketsClient client;
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
// 连接 WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 连接成功");
Serial.print("IP 地址: ");
Serial.println(WiFi.localIP());
// 启动 WebSocket 服务器
server.listen(80);
Serial.println("WebSocket 服务器已启动");
// 注册回调函数
server.onClientConnect([](WebsocketsClient client) {
Serial.println("新客户端连接");
client.send("欢迎连接到 ESP32 WebSocket 服务器");
});
server.onClientDisconnect([](WebsocketsClient client) {
Serial.println("客户端断开连接");
});
server.onMessage([](WebsocketsClient client, WebsocketsMessage message) {
String msg = message.data();
Serial.print("收到消息: ");
Serial.println(msg);
if (msg == "LED_ON") {
digitalWrite(ledPin, HIGH);
client.send("LED 已打开");
} else if (msg == "LED_OFF") {
digitalWrite(ledPin, LOW);
client.send("LED 已关闭");
} else {
client.send("未知指令");
}
});
}
void loop() {
// 轮询客户端事件
server.poll();
}
这段代码跑起来后,你用浏览器打开一个 WebSocket 测试工具(比如 websocket.org 的测试页),连接到 ws://你的ESP32_IP:80,就能收发消息了。
2.6 知识结构图
下面这张图,帮你理清 WebSocket 服务器的整体架构:
2.7 常见问题与避坑
最后,分享几个我踩过的坑:
- WiFi 连接超时:如果 ESP32 连不上 WiFi,服务器就启动不了。建议加个超时重试机制,别死等。
- 端口被占用:默认 80 端口可能被其他服务占了。我习惯用 8080 或者 81,避免冲突。
- 消息格式:客户端发来的消息可能是 JSON,也可能是纯文本。建议统一用 JSON,扩展性好。
- 内存泄漏:每次收到消息都 new 一个对象,用完不 delete,跑几天就崩了。用库的时候注意看文档,确认它有没有自动管理内存。
调试建议:先用 PC 上的 WebSocket 客户端工具测试,确认服务器正常,再写前端代码。这样能快速定位问题是出在服务器还是客户端。