ESP32-Web-Server编程- WebSocket 编程

news2024/11/18 19:32:35

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.coloradmin.cn/o/1270105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux相关--笔试和面试高频

Linux RedHat公司已经宣布停止维护CentOS服务器操作系统&#xff0c;可以选择华为开源的欧拉系统、阿里开源的龙蜥系统和腾讯开源的TencentOS系统 面试 几个基本的Linux命令 pwd #查看当前绝对路径 结果/home/stu touch / vi编辑器 #创建文件 mkdir -p /home/stu/test #当…

智能工厂是什么?

今天就聊聊企业智能工厂的打造&#xff0c;企业想实现数字化转型建立智能工厂&#xff0c;就需要先建设数字化车间&#xff0c;可以说数字化车间是建设智能工厂的重要一环&#xff0c;智能工厂的基础是数字化车间。数字化车间可以实现企业生产过程中车间计划调度、工艺执行管理…

高项备考葵花宝典-项目范围管理输入、输出、工具和技术

项目范围管理包括确保项目“做”且“只做”所需的全部工作&#xff08;即不能少做&#xff0c;也不能多做&#xff0c;如果多做&#xff0c;就要消耗团队额外的时间和资源&#xff0c;并且无法被认可&#xff09;&#xff0c;以成功完成项目。项目范围管理主要在于定义和控制哪…

【MySQL数据库】SQL查询语句总结

目录 一、查询数据 1.1 基本查询语句 1.2 表单查询 1.3 WHERE子句 1.3.1 IN关键字查询 1.3.2 Between查询范围 1.3.3 Like匹配查询 1.3.4 AND多条件查询&#xff08;等同于&&&#xff09; 1.3.5 OR多条件查询&#xff08;等同于||&#xff09; 1.3.6 LIMIT子句 1.3.7 对…

聚观早报 |亚马逊AWS发布新AI芯片;拼多多Q3营收增长94%

【聚观365】11月30日消息 亚马逊AWS发布新AI芯片 拼多多Q3营收增长94% Redmi K70全新国风配色揭晓 英伟达扩大自动驾驶中国团队 华为nova 12参数细节曝光 亚马逊AWS发布新AI芯片 在美国时间周二举办的Reinvent大会上&#xff0c;亚马逊旗下的云计算部门AWS发布了新的人工…

【开源视频联动物联网平台】为什么需要物联网网关?

在一些物联网项目中&#xff0c;物联网网关这一产品经常被涉及。那么&#xff0c;物联网网关究竟有何作用&#xff1f;具备哪些功能&#xff1f;同时&#xff0c;我们也发现有些物联网设备并不需要网关。那么&#xff0c;究竟在何时需要物联网网关呢&#xff1f; 物联网的架构…

linux安装minIo(亲测可用)

一、创建文件夹 进入opt文件夹 cd /opt/创建minio文件夹&#xff1b; mkdir minio赋予权限 chmod 777 minio/执行完后查看目录 进到minio文件夹 创建bin目录 mkdir bin创建data目录 mkdir data创建log touch minio.log创建start.sh文件&#xff0c;并写入数据(不会vi或…

Stream API练习题

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 考虑到Stream API在实际…

abapgit 安装及使用

abapgit 需求 SA[ BASIS 版本 702 及以上 版本查看路径如下&#xff1a; 安装步骤如下&#xff1a; 1. 下载abapgit 独立版本 程序 链接如下&#xff1a;raw.githubusercontent.com/abapGit/build/main/zabapgit_standalone.prog.abap 2.安装开发版本 2.1 在线安装 前置条…

智慧公厕为城市智慧管理提供强力有的数据支持

在当今科技飞速发展的时代&#xff0c;城市管理正面临着前所未有的挑战与机遇。而在这个城市发展的脚步日新月异的同时&#xff0c;一项看似不起眼的技术却正在默默地为城市的智慧管理提供着强有力的支持——那就是智慧公厕。这些不起眼的公共设施不仅仅是人们日常生活的一部分…

Linux系统平均负载

我们经常会使用 top 命令来查看系统的性能情况&#xff0c;在 top 命令的第一行可以看到 load average 这个数据&#xff0c;如下图所示&#xff1a; load average 包含 3 列&#xff0c;分别表示 1 分钟、5 分钟和 15 分钟的 系统平均负载 系统平均负载&#xff1a; 如果将 …

多媒体信号处理复习笔记 --脑图版本

多媒体信号处理复习笔记 --脑图版本 依据 [2020多媒体信号处理复习笔记] 考前复习时使用Xmind制作 例图: PDF下载 BaiduYunPan 提取码&#xff1a;jbyw CSDN 下载

LeetCode2514.统计同位异构字符串数目

题目简单&#xff0c;关键是灵茶山艾府的代码写起来太优美&#xff0c;不得不记录一下 const int Mod 1e97; using ll long long; ll qmi(ll a,ll b,ll mod){ll res 1;while(b){if(b&1)res res*a%mod;aa*a%mod;b>>1;}return res; }class Solution { public:int c…

[黑皮系列] 计算机网络:自顶向下方法(第8版)

文章目录 《计算机网络&#xff1a;自顶向下方法&#xff08;第8版&#xff09;》简介作者目录前言配套公开课 《计算机网络&#xff1a;自顶向下方法&#xff08;第8版&#xff09;》 出版信息&#xff1a; 原作名: Computer Networking: A Top-Down Approach 作者: [美] Jame…

LangChain 15根据问题自动路由Router Chain确定用户的意图

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

ApiSix的docker 容器化部署及使用

⼀&#xff0e;etcd安装 Docekr安装Etcd 环境准备 此处安装&#xff0c;是利⽤下载的 etcd 源⽂件&#xff0c;利⽤ docker build 构建完整镜像&#xff0c;具体操作如下&#xff1a; 1.环境准备 1.1. 新建⽂件夹 在磁盘某个路径下新建⼀个⽂件夹&#xff0c;⽤处操作 Dockerfi…

13:kotlin类和对象 -- 属性(Properties)

定义属性 类属性可使用var和val定义 class Address {var name: String "Holmes, Sherlock"var street: String "Baker"var city: String "London"var state: String? nullvar zip: String "123456" }属性使用 fun copyAddres…

00Hadoop数据仓库平台

在这里是学习大数据的第一站 什么是数据仓库常见大数据平台组件及介绍 什么是数据仓库 在计算领域&#xff0c;数据仓库&#xff08;DW 或 DWH&#xff09;也称为企业数据仓库&#xff08;EDW&#xff09;&#xff0c;是一种用于报告和数据分析的系统&#xff0c;被认为是商业智…

深度学习实现语义分割算法系统 - 机器视觉 计算机竞赛

文章目录 1 前言2 概念介绍2.1 什么是图像语义分割 3 条件随机场的深度学习模型3\. 1 多尺度特征融合 4 语义分割开发过程4.1 建立4.2 下载CamVid数据集4.3 加载CamVid图像4.4 加载CamVid像素标签图像 5 PyTorch 实现语义分割5.1 数据集准备5.2 训练基准模型5.3 损失函数5.4 归…

vuepress-----2、初体验

2、初体验 目标 创建GitHub账号创建Github项目初体验vuepress默认主体的首页 初体验 (opens new window) --- home: true heroImage: /hero.png heroText: Hero 标题 tagline: Hero 副标题 actionText: 快速上手 → actionLink: /zh/guide/ features: - title: 简洁至上deta…