在本教程中,您将学习如何使用 WebSocket 通信协议构建具有 ESP8266 的 Web 服务器。例如,我们将向您展示如何构建一个网页来远程控制ESP8266输出。输出状态显示在网页上,并在所有客户端中自动更新。

该ESP8266将使用 Arduino IDE 和 ESPAsyncWebServer 进行编程。我们也有类似的 ESP32 WebSocket 指南。
如果您一直在关注我们以前的一些 Web 服务器项目,例如这个项目,您可能已经注意到,如果您同时打开了多个选项卡(在相同或不同的设备上),除非您刷新网页,否则状态不会在所有选项卡中自动更新。为了解决这个问题,我们可以使用 WebSocket 协议——当发生更改时,所有客户端都可以收到通知,并相应地更新网页。
WebSocket 简介
WebSocket 是客户端和服务器之间的持久连接,允许双方使用 TCP 连接进行双向通信。这意味着您可以在任何给定时间将数据从客户端发送到服务器,并从服务器发送到客户端。

客户端通过称为 WebSocket 握手的过程与服务器建立 WebSocket 连接。握手以 HTTP 请求/响应开始,允许服务器在同一端口上处理 HTTP 连接以及 WebSocket 连接。建立连接后,客户端和服务器可以全双工模式发送 WebSocket 数据。
使用 WebSockets 协议,服务器(ESP8266板)可以在不请求的情况下向客户端或所有客户端发送信息。这也允许我们在发生更改时向 Web 浏览器发送信息。
这种变化可以是网页上发生的事情(您点击了一个按钮),也可以是发生在ESP8266端的事情,例如按下电路上的物理按钮。
项目概况
这是我们将为这个项目构建的网页。

- ESP8266 Web 服务器显示一个网页,其中包含一个用于切换 GPIO 2 状态的按钮;
- 为简单起见,我们控制 GPIO 2 – 板载 LED。您可以使用此示例来控制任何其他 GPIO;
- 该接口显示当前 GPIO 状态。每当GPIO状态发生变化时,接口就会立即更新;
- GPIO 状态在所有客户端中自动更新。这意味着,如果您在同一设备或不同设备上打开了多个 Web 浏览器选项卡,它们都会同时更新。
它是如何工作的?
下图描述了单击“切换”按钮时发生的情况。

当您单击“切换”按钮时,会发生什么情况:
- 点击“切换”按钮;
- 客户端(您的浏览器)通过 WebSocket 协议发送带有“切换”消息的数据;
- ESP8266(服务器)收到此消息,因此它知道它应该切换 LED 状态。如果 LED 之前熄灭,请将其打开;
- 然后,它通过 WebSocket 协议将具有新 LED 状态的数据发送到所有客户端;
- 客户端接收消息并相应地更新网页上的 LED 状态。这使我们能够在发生更改时几乎立即更新所有客户端。
准备Arduino IDE
我们将使用 Arduino IDE 对 ESP8266 板进行编程,因此请确保已将其安装在 Arduino IDE 中。
安装库 – 异步 Web 服务器
为了构建 Web 服务器,我们将使用 ESPAsyncWebServer 库。该库需要 ESPAsyncTCP 库才能正常工作。单击下面的链接下载库。
这些库无法通过Arduino库管理器进行安装,因此您需要将库文件复制到Arduino安装库文件夹。或者,在Arduino IDE中,您可以转到“程序”>“包含库”>“添加.zip库”,然后选择刚刚下载的库。
ESP8266 NodeMCU WebSocket 服务器的代码
将以下代码复制到 Arduino IDE。
在以下变量中插入您的网络凭据,代码将立即起作用。
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
代码的工作原理
继续阅读以了解代码的工作原理,或跳到演示部分。
导入库
导入必要的库以构建 Web 服务器。
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
网络凭据
在以下变量中插入网络凭据:
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
GPIO输出
创建一个名为 ledState 的变量来保存 GPIO 状态,并创建一个名为 ledPin 的变量来引用要控制的 GPIO。在本例中,我们将控制板载 LED(连接到 GPIO 2)。
bool ledState = 0;
const int ledPin = 2;
AsyncWebServer 和 AsyncWebSocket
在端口 80 上创建一个 AsyncWebServer 对象。
AsyncWebServer server(80);
ESPAsyncWebServer 库包含一个 WebSocket 插件,可以轻松处理 WebSocket 连接。创建一个名为 ws 的 AsyncWebSocket 对象,以处理 /ws 路径上的连接。
AsyncWebSocket ws("/ws");
构建网页
index_html 变量包含构建网页和设置网页样式以及使用 WebSocket 协议处理客户端-服务器交互所需的 HTML、CSS 和 JavaScript。
注意:我们将构建网页所需的所有内容放在我们在Arduino程序上使用的index_html变量上。请注意,将 HTML、CSS 和 JavaScript 文件分开,然后上传到 ESP8266 文件系统并在代码中引用它们可能更实用。
推荐阅读:使用 SPIFFS ESP8266 Web 服务器(SPI 闪存文件系统)
下面是 index_html 变量的内容:
<!DOCTYPE HTML>
<html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
h2{
font-size: 1.5rem;
font-weight: bold;
color: #143642;
}
.topnav {
overflow: hidden;
background-color: #143642;
}
body {
margin: 0;
}
.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
font-size: 1.5rem;
color:#8c8c8c;
font-weight: bold;
}
</style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
<div class="topnav">
<h1>ESP WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<h2>Output - GPIO 2</h2>
<p class="state">state: <span id="state">%STATE%</span></p>
<p><button id="button" class="button">Toggle</button></p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = state;
}
window.addEventListener('load', onLoad);
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
</script>
</body>
</html>
CSS
在<style></style>标记之间,我们包含了使用css设置网页样式的样式。您可以随意更改它,使网页看起来像您所希望的那样。我们不会解释此网页的css是如何工作的,因为它与本WebSocket教程无关。
<style>
html {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
h2 {
font-size: 1.5rem;
font-weight: bold;
color: #143642;
}
.topnav {
overflow: hidden;
background-color: #143642;
}
body {
margin: 0;
}
.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
font-size: 1.5rem;
color:#8c8c8c;
font-weight: bold;
}
</style>
[HTML全文]
在标签之间,我们添加用户可见的网页内容。
<div class="topnav">
<h1>ESP WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<h2>Output - GPIO 2</h2>
<p class="state">state: <span id="state">%STATE%</span></p>
<p><button id="button" class="button">Toggle</button></p>
</div>
</div>
标题 1 中包含文本“ESP WebSocket Server”。随意修改该文本。
<h1>ESP WebSocket Server</h1>
然后,有一个标题 2,其中包含“输出 – GPIO 2”文本。
<h2>Output - GPIO 2</h2>
之后,我们有一个显示当前GPIO状态的段落。
<p class="state">state: <span id="state">%STATE%</span></p>
%STATE% 是 GPIO 状态的占位符。它将被发送网页时的ESP8266替换为当前值。HTML 文本上的占位符应介于 % 符号之间。这意味着这个 %STATE% 文本就像一个变量,然后将被替换为实际值。
将网页发送到客户端后,每当 GPIO 状态发生更改时,状态都需要动态更改。我们将通过 WebSocket 协议接收该信息。然后,JavaScript 会处理如何处理接收到的信息,以相应地更新状态。为了能够使用 JavaScript 处理该文本,文本必须具有我们可以引用的 id。在本例中,id 为 state ()。
最后,有一个段落带有切换 GPIO 状态的按钮。
<p><button id="button" class="button">Toggle</button></p>
请注意,我们已经为按钮指定了一个 id (id=“button”)。
JavaScript – 处理 WebSocket
JavaScript 在标记之间移动。它负责在浏览器中完全加载 Web 界面后立即初始化与服务器的 WebSocket 连接,并通过 WebSocket 处理数据交换。
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = state;
}
window.addEventListener('load', onLoad);
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
</script>
让我们来看看它是如何工作的。
网关是 WebSocket 接口的入口点。
var gateway = `ws://${window.location.hostname}/ws`;
window.location.hostname 获取当前页面地址(Web 服务器 IP 地址)。
创建一个名为 websocket 的新全局变量。
var websocket;
添加一个事件侦听器,该侦听器将在网页加载时调用 onload 函数。
window.addEventListener('load', onload);
onload() 函数调用 initWebSocket() 函数来初始化与服务器的 WebSocket 连接,并使用 initButton() 函数将事件侦听器添加到按钮。
function onload(event) {
initWebSocket();
initButton();
}
initWebSocket() 函数在前面定义的网关上初始化 WebSocket 连接。我们还分配了几个回调函数,用于打开、关闭 WebSocket 连接或接收消息时。
function initWebSocket() {
console.log('Trying to open a WebSocket connection…');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
}
当连接打开时,我们只需在控制台中打印一条消息并发送一条消息说“嗨”。ESP8266收到该消息,因此我们知道连接已初始化。
function onOpen(event) {
console.log('Connection opened');
websocket.send('hi');
}
如果由于某种原因 Web 套接字连接关闭,我们在 2000 毫秒(2 秒)后再次调用 initWebSocket() 函数。
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
setTimeout() 方法在指定的毫秒数后调用函数或计算表达式。
最后,我们需要处理收到新消息时发生的情况。服务器(您的 ESP 板)将发送“1”或“0”消息。根据收到的消息,我们希望在显示状态的段落上显示“ON”或“OFF”消息。还记得那个带有 id=“state” 的标签吗?我们将获取该元素并将其值设置为 ON 或 OFF。
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = state;
}
initButton() 函数通过其 id(按钮)获取按钮,并添加一个类型为“click”的事件侦听器。
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
这意味着当您单击该按钮时,将调用切换函数。
toggle 函数使用 WebSocket 连接发送带有“toggle”文本的消息。
function toggle(){
websocket.send('toggle');
}
然后,ESP8266应处理收到此消息时发生的情况 – 切换当前 GPIO 状态。
处理 WebSocket – 服务器
之前,您已经了解了如何在客户端(浏览器)上处理 WebSocket 连接。现在,让我们来看看如何在服务器端处理它。
通知所有客户
notifyClients() 函数通过一条消息通知所有客户端,其中包含您作为参数传递的任何内容。在这种情况下,每当发生更改时,我们都会通知所有客户端当前 LED 状态。
void notifyClients() {
ws.textAll(String(ledState));
}
AsyncWebSocket 类提供了一个 textAll() 方法,用于将相同的消息发送到同时连接到服务器的所有客户端。
处理 WebSocket 消息
handleWebSocketMessage() 函数是一个回调函数,每当我们通过 WebSocket 协议从客户端接收新数据时,它就会运行。
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
}
}
如果我们收到 “toggle” 消息,我们将切换 ledState 变量的值。此外,我们通过调用 notifyClients() 函数来通知所有客户端。这样,所有客户端都会收到更改通知,并相应地更新界面。
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
配置 WebSocket 服务器
现在,我们需要配置一个事件侦听器来处理 WebSocket 协议的不同异步步骤。可以通过定义 onEvent() 来实现此事件处理程序,如下所示:
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %sn", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnectedn", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
type 参数表示发生的事件。它可以采用以下值:
- WS_EVT_CONNECT客户端登录时;
- WS_EVT_DISCONNECT客户端注销时;
- WS_EVT_DATA从客户端接收到数据包时;
- WS_EVT_PONG响应 ping 请求;
- WS_EVT_ERROR从客户端收到错误时。
初始化 WebSocket
最后,initWebSocket() 函数初始化 WebSocket 协议。
void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}
processor()
processor() 函数负责在 HTML 文本上搜索占位符,并在将网页发送到浏览器之前将它们替换为我们想要的任何内容。在我们的例子中,如果 ledState 为 1,我们将 %STATE% 占位符替换为 ON。否则,请将其替换为 OFF。
String processor(const String& var){
Serial.println(var);
if(var == "STATE"){
if (ledState){
return "ON";
}
else{
return "OFF";
}
}
}
setup()
在 setup() 中,初始化串行监视器以进行调试。
Serial.begin(115200);
将 ledPin 设置为 OUTPUT,并在程序首次启动时将其设置为 LOW。
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
初始化 Wi-Fi 并在串行监视器上打印ESP8266 IP 地址。
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
// Print ESP Local IP Address
Serial.println(WiFi.localIP());
通过调用之前创建的 initWebSocket() 函数来初始化 WebSocket 协议。
initWebSocket();
处理请求
当您在根/URL 上收到请求时,提供保存在 index_html 变量上的文本 – 您需要将处理器函数作为参数传递,以将占位符替换为当前 GPIO 状态。
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
最后,启动服务器。
server.begin();
loop()
LED 将在 loop() 上进行物理控制。
void loop() {
ws.cleanupClients();
digitalWrite(ledPin, ledState);
}
请注意,我们都调用 cleanupClients() 方法。原因如下(来自 ESPAsyncWebServer 库 GitHub 页面的解释):
浏览器有时无法正确关闭 WebSocket 连接,即使在 JavaScript 中调用 close() 函数也是如此。这最终会耗尽 Web 服务器的资源,并导致服务器崩溃。定期从 main loop() 调用 cleanupClients() 函数,通过在超过最大客户端数时关闭最旧的客户端来限制客户端数量。这可以每个周期调用,但是,如果您希望使用更少的功率,那么每秒调用一次的频率就足够了。
示范
在 ssid 和 password 变量上插入网络凭据后,您可以将代码上传到您的主板。不要忘记检查您是否选择了正确的电路板和 COM 端口。
上传代码后,以 115200 的波特率打开串行监视器,然后按下板载 EN/RST 按钮。应打印 ESP IP 地址。
在本地网络上打开浏览器并插入ESP8266 IP 地址。您应该可以访问网页以控制输出。

单击按钮以切换 LED。您可以同时打开多个 Web 浏览器选项卡或从不同的设备访问 Web 服务器,每当有更改时,LED 状态都会在所有客户端中自动更新。
总结
在本教程中,你学习了如何使用 ESP8266 设置 WebSocket 服务器。WebSocket 协议允许客户端和服务器之间进行全双工通信。初始化后,服务器和客户端可以在任何给定时间交换数据。
这非常有用,因为每当发生某些事情时,服务器都可以向客户端发送数据。例如,您可以向此设置添加一个物理按钮,按下该按钮会通知所有客户端更新 Web 界面。
在此示例中,我们向您展示了如何控制ESP8266的一个 GPIO。您可以使用此方法控制更多 GPIO。您还可以使用 WebSocket 协议在任何给定时间发送传感器读数或通知。



















It’s crucial to prioritize security when choosing an online casino – verifying identity upfront, like with downloading apps login, is a smart sign. Responsible gaming & platform trust are key, especially with evolving regulations!
Just found this gem for strategic slot play! The analytics tools here are top-notch. Check out cardtime download apk to get started today. Perfect for serious players seeking competitive entertainment in the region without the usual hassle of delayed withdrawals or lack of transparency. Highly recommended if you want advanced features and robust security protocols right away.