ESP32-Web-Server编程- WebSocket 编程

ESP32-Web-Server编程- WebSocket 编程

概述

在前述 ESP32-Web-Server 实战编程-通过网页控制设备的 GPIO 中,我们创建了一个基于 HTTP 协议的 ESP32 Web 服务器,每当浏览器向 Web 服务器发送请求,我们将 HTML/CSS 文件提供给浏览器。

在这里插入图片描述

使用 HTTP 服务器库的缺点是,**如果多个客户端连接到 Web 服务器,当 A 浏览器改变了网页的内容(比如点餐系统),它不会自动更新网页上的内容到所有客户端B、C。我们可以通过使用 WebSocket 通信协议来解决此问题。**例如,如果多个客户端连接到 Web 服务器,并且任何一个客户端更改了设备的 GPIO 引脚的状态,则它将自动向所有连接的客户端通知该更改状态。

在这里插入图片描述
相比 HTTP 协议,WebSocket 通信协议除了可以双向通信、并且向多个客户端同时发送通知信息外,还可以提供持久连接,并且由于没有为每个请求重新建立连接的开销,因此延迟较低。

在这里插入图片描述

需求及功能解析

本节演示如何在 ESP32 上实现一个WebSocket 服务器。示例仍旧以在网页控制一个 GPIO 为例子。

与前述不同的是,通过 WebSocket 服务器,当多个浏览器在访问该服务器时,不同的浏览器之间可以及时接收到网页更新的信息。

示例解析

前端代码

示例中的 ESP32 WebSocket 服务器前端代码在 data\index.html 文件中,其主要提供两个功能:

  • 显示 LED(对应设备的一个 GPIO)的状态,并为网页提供一个按钮用于切换 LED 的状态。

    <div class="topnav"><h1>ESP32 WebSocket Server</h1>
    </div>
    <div class="content"><div class="card"><h2>ONBOARD LED GPIO2</h2><p><button id="button" class="button">Toggle LED</button></p><p class="state">State: <span id="state">%s</span></p></div>
    </div>
    </div>
    
  • script中,每当 LED 的状态发生更新时,将 LED 状态作为 WebSocket 消息发送到所有连接的客户端。

    // 当网页加载时将自动调用该函数
    function onLoad(event) {initWebSocket();initButton();
    }
    // 此函数负责初始化页面上的按钮元素并向其附加事件侦听器。单击该按钮时,它会通过 WebSocket 协议向 ESP32 发送消息,以切换 LED 的状态。
    function initButton() {document.getElementById('button').addEventListener('click', toggle);
    }
    
  • 在该 JavaScript 代码中,其定义了一个变量“gateway”,即 WebSocket 的端点,其相当于 HTTP 中的 URL。

  • 点击“切换 LED”按钮后,会通过 WebSocket 向 ESP32 发送消息,切换 LED 的状态,并更新页面上的状态文本以反映 LED 的当前状态。

  • 该代码中还利用了 console.log() 函数,该函数会将消息输出到浏览器的开发人员控制台。这对于调试和理解代码流非常有用。

后端代码

通过 HTTP 建立 wrbsocket 握手

WebSocket 服务器通过 HTTP 协议握手,然后开始使用 WebSocket 通信协议进行数据通信。因此,我们需要设置一个HTTP GET请求处理程序来完成最初始的建立握手的环节。
get_req_handler()函数用于响应该握手阶段的 HTTP 请求。

esp_err_t get_req_handler(httpd_req_t *req)
{int response;if(led_state){sprintf(response_data, index_html, "ON");}else{sprintf(response_data, index_html, "OFF");}response = httpd_resp_send(req, response_data, HTTPD_RESP_USE_STRLEN);return response;
}

handle_ws_req(httpd_req_t *req) 负责处理从所有 Web 客户端发送到服务器的 WebSocket 请求。

WebSocket 接收客户端的数据

函数 handle_ws_req(httpd_req_t *req) 负责处理从所有 Web 客户端发送到服务器的 WebSocket 请求(包括打开、关闭和处理通过 websocket 连接发送的数据)。

static esp_err_t handle_ws_req(httpd_req_t *req)

该函数首先检查请求的方法是否是 HTTP 协议的 HTTP_GET,如果是,它将打印一条消息,指示 WebSocket 握手阶段(因为 WebSocket Web 服务器通过 HTTP 握手开始初始通信,然后遵循 WebSocket 通信协议)已完成,WebSocket 连接已打开,函数返回。

if (req->method == HTTP_GET)
{ESP_LOGI(TAG, "Handshake done, the new connection was opened");return ESP_OK;
}

如果请求的方法不是HTTP_GET,则在该示例中表示客户端请求正在发送 WebSocket 数据帧。将调用函数 httpd_ws_recv_frame() 来接收 WebSocket 数据帧并将其存储在 ws_pkt 变量中。

esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);if (ret != ESP_OK){ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);return ret;}

如果接收到的数据帧长度不为零,则该函数将调用 calloc(1, ws_pkt.len + 1) 为 buf 变量分配内存,该变量将用于存储数据帧的有效负载。然后,它再次调用 httpd_ws_recv_frame() 来检索数据框的有效负载并将其存储在 buf 变量中。该函数记录收到的消息和帧的长度。

 if (ws_pkt.len){buf = calloc(1, ws_pkt.len + 1);if (buf == NULL){ESP_LOGE(TAG, "Failed to calloc memory for buf");return ESP_ERR_NO_MEM;}ws_pkt.payload = buf;ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);if (ret != ESP_OK){ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);free(buf);return ret;}ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);}ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);

最后,该函数检查收到的消息是否为“切换”,如果是,则调用 trigger_async_send(req->handle, req) 函数来通知连接的其他客户端。

if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&strcmp((char *)ws_pkt.payload, "toggle") == 0){free(buf);return trigger_async_send(req->handle, req);}return ESP_OK

总之,此函数通过处理 WebSocket 数据帧、接收请求中发送的消息以及通过向所有连接的客户端发送异步消息来处理消息来处理 WebSocket 请求。

WebSocket 发送方函数

以下两个函数响应 WebSocket 并将帧发送到所有连接的客户端:

static void ws_async_send(void *arg)
static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)

trigger_async_send()负责调用 ws_async_send(),通过使用 httpd_queue_work()**对ws_async_send()进行排队并传递服务器句柄:

static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
{struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));resp_arg->hd = req->handle;resp_arg->fd = httpd_req_to_sockfd(req);return httpd_queue_work(handle, ws_async_send, resp_arg);
}

ws_async_send() 执行以下功能:

  • 切换 led_state 变量的状态,该变量跟踪 LED 的状态。
  • 根据led_state更新指示灯的状态。
  • 使用当前 led_state 格式化要发送的字符串,以生成 Web 套接字数据包的有效负载。
  • 使用 httpd_ws_send_frame_async 函数将数据包发送到所有连接的客户端。
  • 最后,释放为 resp_arg 分配的内存。

示例效果

通过 WebSocket 实现多个浏览器客户端连接到 Web 服务器时可以同步更新同一个网页的内容:

在这里插入图片描述

讨论

1)HTTP 传输协议与 WebSocket 在使用场景上有哪些不同?

HTTP在处理静态数据且不定期更新的应用程序中更可取。

WebSocket在处理实时数据的应用程序中更为可取。比如使用动态数据并期望持续和频繁更新的应用程序,游戏应用程序社交软件必须与多个用户建立联系,这种类型的应用程序可以选择WebSocket来处理实时数据。

2)WebSocket 在前端中的 onload() 事件怎么理解?

即在网页加载时,自动触发的函数,这是浏览器默认的行为,是一个标准。还有其他称为“onOpen()“,“onClose()”,“onMessage()" 等的函数,它们处理WebSocket 上可能发生的不同事件。

总结

1)本节主要是介绍在 ESP32 上实现 WebSocket 服务器。相比 HTTP 协议,WebSocket 通信协议除了可以双向通信、并且向多个客户端同时发送通知信息外,还可以提供持久连接,并且由于没有为每个请求重新建立连接的开销,因此延迟较低。

资源链接

1)ESP32-Web-Server ESP-IDF系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)

3)下一篇:ESP32-Web-Server编程- 使用SSE 实时更新设备信息

(码字不易感谢点赞或收藏)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/231575.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0开始学习JavaScript--JavaScript 中 `let` 和 `const` 的区别及最佳实践

在JavaScript中&#xff0c;let 和 const 是两个用于声明变量的关键字。尽管它们看起来很相似&#xff0c;但它们之间有一些重要的区别。本篇博客将深入探讨 let 和 const 的用法、区别&#xff0c;并提供一些最佳实践&#xff0c;以确保在代码中正确使用它们。 let 和 const …

U-boot(七):U-boot移植

本文主要探讨基于210官方U-boot源码移植。 移植基础 tar -jxvf android_uboot_smdkv210.tar.bz2cd u-boot-samsung-devrm -rf onenand_ipl onenand_bl1 lib_avr32 lib_blackfin lib_i386 lib_m68k lib_mips lib_microblaze lib_nios lib_nios2 lib_ppc lib_sh lib_sparccd bo…

11.兔子生崽问题【2023.11.26】

1.问题描述 有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c;小兔子长到第三个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c;问 第二十个月的兔子对数为多少对&#xff1f; 2.解决思路 3.代码实现 #include<stdio.h> int mai…

Linux:windows 和 Linux 之间文本格式转换

背景 在 Windows 上编辑的文件&#xff0c;放到 Linux 平台&#xff0c;有时会出现奇怪的问题&#xff0c;其中有一个是 ^M 引起的&#xff0c;例如这种错误&#xff1a; /bin/bash^M: bad interpreter 这个问题相信大家也碰到过&#xff0c;原因是 Windows 和 Linux 关于换行的…

Javase | Java常用类 (不断补充中...)

目录: 1.Object类2.String类3.StringBuffer类4.Math类5.Random类6.包装类(不断补充中...) 1.Object类 Object类是Java语言中的所有类的超类&#xff0c;即所有类的根。它中描述的所有方法&#xff0c;所有类都可以使用。 equals( ) : 指示其他某个对象与此对象“是否相等” (比…

PC端数据列表有头像显示头像,没有头像显示名字的第一个字

PC端数据列表有头像显示头像&#xff0c;没有头像显示名字的第一个字 .charAt(0) 是 JavaScript 字符串对象的方法&#xff0c;用于获取字符串的第一个字符。 字符串中的字符位置是从 0 开始的&#xff0c;所以.charAt(0) 就表示获取字符串的第一个字符。 <el-table ref&qu…

layui提示框没有渲染bug解决

bug&#xff1a;使用layui时或许是依赖导入又或是ideal和浏览器缓存问题导致前面明明正常的页面显示&#xff0c;后面出现提示框没有css样式&#xff0c;弹出框没有背景css 效果如下 解决后 解决方法 在你的代码中引入layer.js 我这是jsp页面 <script type"text/jav…

PHP微信UI在线聊天系统源码 客服私有即时通讯系统 附安装教程

DuckChat是一套完整的私有即时通讯解决方案&#xff0c;包含服务器端程序和各种客户端程序&#xff08;包括iOS、Android、PC等&#xff09;。通过DuckChat&#xff0c;站点管理员可以快速在自己的服务器上建立私有的即时通讯服务&#xff0c;用户可以使用客户端连接至此服务器…

Parasoft:正确的静态应用程序安全测试 (SAST) 解决方案

随着软件开发从Web应用扩展到工业物联网&#xff08;IIoT&#xff09;设备&#xff0c;静态应用安全测试&#xff08;SAST&#xff09;越来越有必要从根本上帮助确保软件的功能安全。根据 Forrester Research的研究&#xff0c;网络攻击是近两年安全漏洞的主要来源。因此&#…

聚类分析例题 (多元统计分析期末复习)

例一 动态聚类&#xff0c;K-means法&#xff0c;随机选取凝聚点&#xff08;题目直接给出&#xff09; 已知5个样品的观测值为&#xff1a;1&#xff0c;4&#xff0c;5&#xff0c;7&#xff0c;11。试用K均值法分为两类(凝聚点分别取1&#xff0c;4与1&#xff0c;11) 解&…

深入理解 Vue 中的指针操作(二)

文章目录 ☘️引言☘️基本用法&#x1f342;v-for指令&#x1f342;v-model指令&#x1f331;v-model适用表单控件 ☘️结论 ☘️引言 Vue.js 是一款非常流行且功能强大的前端框架&#xff0c;它以其响应式的数据绑定和组件化的开发方式赢得了众多开发者的喜爱。而在 Vue.js …

[node] Node.js 中Stream流

[node] Node.js 中Stream流 什么是 Stream流操作从流中读取数据写入流管道流链式流 什么是 Stream Stream 是一个抽象接口&#xff0c;Node 中有很多对象实现了这个接口。例如&#xff0c;对http 服务器发起请求的request 对象就是一个 Stream&#xff0c;还有stdout&#xff…