ESP-C3入门14. 实现基本的web server

news2024/11/15 6:10:02

ESP-C3入门14. 实现基本的web server

  • 一、ESP32 IDF创建WEB SERVER的流程
    • 1. 配置web服务器
    • 2. 注册 URI处理器
    • 3. 实现 URI处理器函数
    • 4. 处理HTTP请求
    • 5. 处理web socket连接
    • 6. 注册 URI 处理函数
    • 7. 启动HTTP服务器
    • 8. 发送响应
    • 9. 关闭 http 服务
  • 二、本要主要使用API的说明
    • 1. httpd_register_uri_handler
    • 2. httpd_handle_t
    • 3. httpd_query_key_value获取变量值
    • 4. 获取get参数示例
    • 5. 获取post参数示例
  • 三、基本用法完整示例
    • 1. 加载http_server模块
    • 2. 建立 web_server.h 头文件
    • 3. web_server.c文件实现
    • 4. main.c 创建任务开启web server

在这里插入图片描述

一、ESP32 IDF创建WEB SERVER的流程

1. 配置web服务器

在ESP-IDF中,Web服务器使用httpd组件实现。我们需要先创建httpd_config_t结构体,指定服务器的端口、最大并发连接数、URI匹配处理器等选项。然后,我们通过调用httpd_start函数来启动Web服务器。

httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;

// 设置服务器端口为80
config.server_port = 80;

// 创建HTTP服务器句柄
if (httpd_start(&server, &config) != ESP_OK) {
    printf("Error starting server!\n");
    return;
}

在这个示例中,我们首先使用HTTPD_DEFAULT_CONFIG宏创建默认的Web服务器配置。接着,我们将服务器端口设置为80,创建HTTP服务器句柄,并启动Web服务器。

2. 注册 URI处理器

在Web服务器启动后,我们需要为不同的URI注册处理器函数。当Web服务器接收到请求时,会根据请求的URI选择相应的处理器函数进行处理。在ESP-IDF中,我们可以使用httpd_register_uri_handler函数注册URI处理器。该函数的原型如下:

esp_err_t httpd_register_uri_handler(httpd_handle_t hd, const httpd_uri_t *uri)

其中,hd参数为HTTP服务器句柄;uri参数为包含URI路径、HTTP方法、处理函数等信息的结构体指针。例如:

static esp_err_t hello_get_handler(httpd_req_t *req)
{
    char resp_str[64];
    httpd_req_get_url_query_str(req, resp_str, sizeof(resp_str));
    printf("query string: %s\n", resp_str);
    httpd_resp_send(req, "Hello, world!", HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

httpd_uri_t hello = {
    .uri       = "/hello",
    .method    = HTTP_GET,
    .handler   = hello_get_handler,
    .user_ctx  = NULL
};

if (httpd_register_uri_handler(server, &hello) != ESP_OK) {
    printf("Error registering URI handler!\n");
    return;
}

3. 实现 URI处理器函数

在注册URI处理器后,我们需要实现对应的处理器函数。URI处理器函数的原型为:

typedef esp_err_t (*httpd_uri_func_t)(httpd_req_t *req);

其中,req参数为指向HTTP请求信息的结构体指针,包含了请求的各种参数和数据。

4. 处理HTTP请求

在URI处理器函数中,我们可以通过HTTP请求信息结构体指针httpd_req_t获取HTTP请求的各种参数和数据。以下是一些常用的HTTP请求处理函数:

  • httpd_req_get_hdr_value_str:获取HTTP请求头中指定字段的值(字符串格式)
  • httpd_req_get_url_query_str:获取HTTP请求URL中的查询参数(字符串格式)
  • httpd_query_key_value:解析HTTP请求URL中的查询参数,获取指定参数名的值(字符串格式)
  • httpd_req_recv:从HTTP请求接收数据
  • httpd_req_send:发送HTTP响应数据
  • httpd_resp_set_type:设置HTTP响应内容的MIME类型
  • httpd_resp_send_chunk:分块发送HTTP响应数据。
    例如,以下是一个URI处理器函数的示例,用于处理/echo路径的POST请求:
static esp_err_t echo_post_handler(httpd_req_t *req)
{
    char buf[1024];
    int ret, remaining = req->content_len;

    // 从HTTP请求中接收数据
    while (remaining > 0) {
        ret = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
        if (ret <= 0) {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
                // 处理超时
                httpd_resp_send_408(req);
            }
            return ESP_FAIL;
        }

        // 处理接收到的数据
        // ...

        remaining -= ret;
    }

    // 发送HTTP响应
    httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
    httpd_resp_send(req, "Received data: ", -1);
    httpd_resp_send_chunk(req, buf, req->content_len);
    httpd_resp_send_chunk(req, NULL, 0);

    return ESP_OK;
}

5. 处理web socket连接

除了支持HTTP请求外,ESP-IDF的Web服务器还支持WebSocket连接。WebSocket是一种基于TCP的协议,可以提供双向通信功能。在ESP-IDF中,我们可以使用httpd_ws_frame_t结构体表示WebSocket帧,使用httpd_ws_send_frame_async函数异步发送WebSocket帧。

要处理WebSocket连接,我们需要为WebSocket URI注册专门的处理器函数,并在该函数中处理WebSocket连接的各种事件。

6. 注册 URI 处理函数

创建好HTTP服务器后,需要注册URI处理函数,以便处理客户端发送的请求。URI处理函数需要实现在HTTP请求中指定的URI。

在ESP-IDF中,可以使用httpd_register_uri_handler()函数注册URI处理函数。该函数需要传入一个httpd_uri_t结构体作为参数,该结构体包含了URI路径和处理函数的信息。
例如,下面的代码注册了一个处理根目录的URI处理函数:

httpd_uri_t uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = hello_get_handler,
    .user_ctx  = NULL
};

httpd_register_uri_handler(server, &uri);

上面的代码注册了一个HTTP GET方法,URI路径为“/”的处理函数hello_get_handler。当客户端请求根目录时,HTTP服务器将调用hello_get_handler函数处理请求。

7. 启动HTTP服务器

在所有的URI处理函数都被注册后,可以调用httpd_start()函数启动HTTP服务器。

httpd_start(&server);

8. 发送响应

在URI处理函数中,可以使用httpd_resp_send()函数将响应发送回客户端。该函数需要传入一个httpd_req_t结构体作为参数,该结构体表示HTTP请求和响应。

例如,在上面的hello_get_handler处理函数中,可以使用httpd_resp_send()函数将“Hello, World!”字符串作为响应发送回客户端:

static esp_err_t hello_get_handler(httpd_req_t *req)
{
    const char* resp_str = "Hello, World!";
    httpd_resp_send(req, resp_str, strlen(resp_str));
    return ESP_OK;
}

9. 关闭 http 服务

使用httpd_stop()函数,该函数用于停止HTTP服务并释放所有资源。

// 创建HTTP服务
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_start(&server, &config);

// 关闭HTTP服务
httpd_stop(server);

二、本要主要使用API的说明

1. httpd_register_uri_handler

用于将HTTP请求的URI路由到处理程序。这个函数接收两个参数:httpd_handle_t类型的HTTP服务器句柄和httpd_uri_t类型的URI配置。

2. httpd_handle_t

httpd_handle_t是HTTP服务器的一个句柄,它是通过httpd_start函数创建的。而httpd_uri_t则定义了HTTP请求的URI信息,包括URI路径、HTTP请求方法和处理函数等。

3. httpd_query_key_value获取变量值

httpd_query_key_value 用于从查询字符串中获取指定键的值。查询字符串是指URL中?后面的部分,包含多个键值对,每个键值对之间使用&分隔。例如,对于以下URL:
http://192.168.1.1/path/to/handler?key1=value1&key2=value2
获取其中的:
esp_err_t httpd_query_key_value(const char *query, const char *key, char *buf, size_t buf_len);

这是一个使用示例:

char query_str[] = "key1=value1&key2=value2";
char key[] = "key1";
char value[16];

if (httpd_query_key_value(query_str, key, value, sizeof(value)) == ESP_OK) {
    printf("value=%s\n", value);
} else {
    printf("key not found\n");
}

4. 获取get参数示例

下面定义的 handler 演示了如何从请求参数里解析 字符串param1和整型变量param2:

 esp_err_t index_handler(httpd_req_t *req)
{
     char* query_str = NULL;
     char param1_value[10] = {0};
     int param2_value=0;

     query_str = strstr(req->uri, "?");
     if(query_str!=NULL){
         query_str ++;
         httpd_query_key_value(query_str, "param1", param1_value, sizeof(param1_value));
         char param2_str[10] = {0};
         httpd_query_key_value(query_str, "param2", param2_str, sizeof(param2_str));
         param2_value = atoi(param2_str);
     }

     char resp_str[50] = {0};
     snprintf(resp_str, sizeof(resp_str), "param1=%s, param2=%d", param1_value, param2_value);
    httpd_resp_send(req, resp_str, strlen(resp_str));
    return ESP_OK;
}

5. 获取post参数示例

下面的示例代码中根据httpd_req_t的content_len来分配一个缓冲区,并解析请求中的POST参数:


 esp_err_t post_demo_handler(httpd_req_t *req)
{
    char post_string[64];
    int post_int=0;

    if (req->content_len > 0)
    {
        // 从请求体中读取POST参数
        char *buf = malloc(req->content_len + 1);
        int ret = httpd_req_recv(req, buf, req->content_len);
        if (ret <= 0)
        {
            // 接收数据出错
            free(buf);
            return ESP_FAIL;
        }
        buf[req->content_len] = '\0';

        // 解析POST参数
        char *param_str;
        param_str = strtok(buf, "&");
        while (param_str != NULL)
        {
            char *value_str = strchr(param_str, '=');
            if (value_str != NULL)
            {
                *value_str++ = '\0';
                if (strcmp(param_str, "post_string") == 0)
                {
                    strncpy(post_string, value_str, sizeof(post_string));
                }
                else if (strcmp(param_str, "post_int") == 0)
                {
                    post_int = atoi(value_str);
                }
            }
            param_str = strtok(NULL, "&");
        }

        free(buf);
    }

    // 将结果打印输出
    printf("post_string=%s, post_int=%d\n", post_string, post_int);

    // 返回成功
    httpd_resp_send(req, NULL, 0);
    return ESP_OK;
}


 httpd_uri_t post_uri = {
         .uri = "/post",
         .method = HTTP_POST,
         .handler = post_demo_handler,
         .user_ctx = NULL
 };

三、基本用法完整示例

接前文的项目代码示例,项目结构如下:
在这里插入图片描述
本文主要是使用其中的web_server.c文件。

1. 加载http_server模块

在 CMakeLists.txt里写上 :

idf_component_register(
	REQUIRES "esp_http_server"
)

2. 建立 web_server.h 头文件


#ifndef ESP32_LEARN_WEB_SERVER_H
#define ESP32_LEARN_WEB_SERVER_H
void http_server_task(void *pvParameters);
#endif //ESP32_LEARN_WEB_SERVER_H

3. web_server.c文件实现


#include "include/web_server.h"
#include <http_parser.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "string.h"
/* Define the server port number */
#define SERVER_PORT 80

/* Define the routes for the web server */
const  char *INDEX_HTML = "<html><body><h1>Hello, world</h1></body></html>";

/* Define the handler for the HTTP requests */
 esp_err_t index_handler(httpd_req_t *req)
{
    httpd_resp_send(req, INDEX_HTML, strlen(INDEX_HTML));
    return ESP_OK;
}

/* Define the HTTP server configuration */
 httpd_uri_t index_uri = {
        .uri = "/",
        .method = HTTP_GET,
        .handler = index_handler,
        .user_ctx = NULL
};

/* Define the HTTP server task */
 void http_server_task(void *pvParameters)
{
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.server_port = SERVER_PORT;

    /* Start the HTTP server */
    if (httpd_start(&server, &config) == ESP_OK) {
        /* Register the routes */
        httpd_register_uri_handler(server, &index_uri);
        ESP_LOGI("HTTP_SERVER", "Server started");
    }

    /* Loop to keep the task running */
    while (1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

4. main.c 创建任务开启web server

#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include <nvs_flash.h>
#include "network/include/wifi.h"
#include "network/include/wifi_sta.h"
#include "network/include/wifi_ap.h"
#include "network/include/web_server.h"
static const char *TAG = "main";



void app_main()
{
    ESP_LOGE(TAG, "app_main");
    // 初始化NVS存储区
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // Wi-Fi初始化
    ESP_LOGI(TAG, "Wi-Fi initialization");
    wifi_initialize();

    wifi_init_softap();
    /* Start the HTTP server task */
    xTaskCreate(http_server_task, "http_server_task", 4096, NULL, 5, NULL);
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

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

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

相关文章

编码器的作用和功能有哪些

编码器的作用和功能有哪些 最近很多咨询编码器的作用的&#xff0c;小编结合一下线上和线下的资料&#xff0c;总结了一下&#xff0c;供大家参考。 要说编码器&#xff0c;它的作用很多&#xff0c;主要是用来测量机械运动的速度、位置、角度、距离或计数的&#xff0c;编码器…

SkyWalking使用案例-2

文章目录SkyWalking实现基于容器环境Dubbo微服务链路跟踪部署Dubbo Provider构建Dubbo Provider镜像运行dubbo-provider部署Dubbo Consumer构建Dubbo Consumer镜像运行dubbo-consumer验证SkyWalking收集python项目数据Skywalking告警Skywalking指标Skywalking告警规则SkyWalkin…

62. 不同路径

62. 不同路径 一个机器人位于一个 m∗nm * nm∗n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路…

virtualbox7虚拟机中安装苹果macOS big sur系统详细教程

第1步&#xff0c;在 Windows 10/11 PC 上启用虚拟化。 现在的电脑一般都默认开启虚拟化技术了。 如果你遇到一些报错&#xff0c;比如收到错误消息“无法在虚拟机上打开会话”&#xff0c;可以查看 如果没有遇到问题&#xff0c;可以直接进入到第二步。 第2步&#xff0c;在…

【Spark分布式内存计算框架——Spark Streaming】12. 偏移量管理(上)代码重构与Checkpoint 恢复

6. 偏移量管理 针对前面实现【百度热搜排行榜Top10】实时状态统计应用来说&#xff0c;当应用关闭以后&#xff0c;再次启动&#xff08;Restart&#xff09;执行&#xff0c;并没有继续从上次消费偏移量读取数据和获取以前状态信息&#xff0c;而是从最新偏移量&#xff08;L…

JVM 不同垃圾回收器的日志格式分析

1、GC日志采集 在服务器上我们需要配置一些参数才能采集到历史的GC日志信息&#xff0c;这些参数通常在项目启动的时候就需要指定&#xff0c; 如果你项目是jar包&#xff0c;可以按照下面方式指定这些GC参数即可。 下面这些参数意思是把GC日志记录到/opt/app/abc-user/ard-…

蓝桥杯备赛——Echarts学习

文章目录前言学习 ECharts 的方法快速上手基础知识option 配置选项可选配置title 标题组件tooltip 提示框组件axisPointer 坐标轴指示器legend 图例组件toolbox 工具栏坐标轴xAxis和yAxisseries &#xff08;[ ]用数组表示,数组里是一个个数据对象&#xff09;饼状图散点图交互…

盘点代码情诗集合❤,程序员表白的巅峰之作,特此奉献

程序员怎么表白&#xff1f;写代码啊&#xff01;每到情人节&#xff0c;程序员们就纷纷出动&#xff0c;各种别出心裁的表白代码倾囊相送。我曾被大批表白代码砸晕&#xff0c;沉浸在“虚拟的”幸福感中不能自拔。我在众多代码中精选了以下几十条&#xff0c;每一条都是文学素…

Python中的遍历字典的键和值

一、Python的字典在项目的开发过程中&#xff0c;如果遇到有映射关系的内容可以考虑使用Python中的字典进行存储数据&#xff0c;字典中冒号前的数据称为【键】、冒号后的数据称为【值】。二、Python字典的用法2.1、Python的定义#Python字典的定义 字典名称{键1:值1,键2:值2,键…

JavaScript Date 日期对象

文章目录JavaScript Date 日期对象Date 对象Date 对象属性Date 对象方法创建日期设置日期两个日期比较JavaScript Date 日期对象 日期对象用于处理日期和时间。 Date 对象 Date 对象用于处理日期与实际。 创建 Date 对象&#xff1a; new Date(). 以上四种方法同样可以创建…

Validate端口使用手册

知行之桥EDI系统从2020版本开始引入了Validate端口&#xff0c;用来实现对XML数据文件进行一些规则的验证&#xff0c;保证XML数据文件的有效性。本文将介绍如何使用Validate端口。 端口创建 同其他功能性端口一样&#xff0c;只需要将Validata端口从左侧的端口清单拖拽到右侧…

子数组达到规定累加和的最大长度系列问题

文章目录1、题目一&#xff1a;正整数数组中子数组累加和 KKK 最大长度1.1 题目描述1.2 思路分析1.3 代码实现2、题目二&#xff1a;整数数组中子数组累加和为 KKK 的最大长度2.1 题目描述2.2 思路分析2.3 代码实现2.4 引申变形2.5 技巧应用题2.5.1 剑指 Offer II 010. 和为 k …

关于HTTP/3的小知识点

客户端用 TCP 发送了三个包&#xff0c;但服务器所在的操作系统只收到了后两个包&#xff0c;第一个包丢了。那么内核里的 TCP 协议栈就只能把已经收到的包暂存起来&#xff0c;“停下”等着客户端重传那个丢失的包&#xff0c;这样就又出现了“队头阻塞”。由于这种“队头阻塞…

Kubernetes之存储管理(中)

NFS网络存储 emptyDir和hostPath存储&#xff0c;都仅仅是把数据存储在pod所在的节点上&#xff0c;并没有同步到其他节点&#xff0c;如果pod出现问题&#xff0c;通过deployment会产生一个新的pod&#xff0c;如果新的pod不在之前的节点&#xff0c;则会出现问题&#xff0c…

CV——day81(1) 读论文: 基于自监督一致性学习的驾驶场景交通事故检测(有源码)

Traffic Accident Detection via Self-Supervised Consistency Learning in Driving Scenarios 基于自监督一致性学习的驾驶场景交通事故检测I. INTRODUCTIONIII. OUR APPROACHA. 帧预测B. 物体位置预测C. 驾驶场景上下文表示(DSCR)D. 协作多任务一致性学习E.交通事故判定IV. E…

UART串口通信协议

一、协议 1.1 消息格式 串口协议是一种全双工、异步通信协议&#xff0c;不需要同步时钟&#xff0c;数据的发送是一位一位的发送&#xff0c;完整的一帧数据通常由起始位、数据、奇偶校验位和停止位组成 1.2 波特率 为确保正确的收发信息&#xff0c;双方必须设置相同的波…

火山引擎 DataLeap:揭秘字节跳动数据血缘架构演进之路

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 DataLeap 是火山引擎数智平台 VeDI 旗下的大数据研发治理套件产品&#xff0c;帮助用户快速完成数据集成、开发、运维、治理、资产、安全等全套数据中台建设&#x…

MySQL 中的锁有哪些类型,MySQL 中加锁的原则

锁的类型MySQL 找那个根据加锁的范围&#xff0c;大致可以分成全局锁&#xff0c;表级锁和行级锁。全局锁全局锁&#xff0c;就是对整个数据库加锁。加锁flush tables with read lock解锁unlock tables全局锁会让整个库处于只读状态&#xff0c;之后所有的更新操作都会被阻塞&a…

OB运维 | 连接 kill 中的 session_id

作者&#xff1a;姚嵩 外星人… 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 背景&#xff1a; 通过 obproxy 连接 OB 后&#xff0c;发现&#xff1a; kill 命令使⽤ show processli…

如何写出让人看不懂的MATLAB代码?

最近呢有不少好奇的伙伴私下问咱这是怎么实现大幅度降低matlab代码可读性。于是咱准备将相关的资源分享给大家&#xff0c;这个工具的根来源于大神thrynae (Rik)公开分享一款名叫minify的小工具图片。 咱也不藏着掖着&#xff0c;其实大家用minify作为关键词检索&#xff0c;不…