ESP32_IDF学习8【HTTP服务器】
TCP/IP组件
ESP系列提供了实现TCP/IP协议栈的库函数,#include <esp_netif.h>
即可使用这些库函数
特点如下:
- 提供TCP/IP协议栈的应用抽象层
- 提供线程保护
- 目前只用于lwIP TCP/IP协议栈(lwIP:Light Weight IP Protocol,支持在嵌入式设备中使用的小型TCP/IP协议栈,占用内存较少)
- 具有丰富的API库函数
- 大多数情况下,应用程序不需要直接调用组件的API,而是从默认的网络事件处理函数中调用
- 不兼容idf4.1以下使用的TCP/IP适配器相关函数,需修改代码进行迁移
ESP-NETIF结构如下(摘自官方文档)
| (A) USER CODE |
| |
.............| init settings events |
. +----------------------------------------+
. . | *
. . | *
--------+ +===========================+ * +-----------------------+
| | new/config get/set | * | |
| | |...*.....| init |
| |---------------------------| * | |
init | | |**** | |
start |********| event handler |*********| DHCP |
stop | | | | |
| |---------------------------| | |
| | | | NETIF |
+-----| | | +-----------------+ |
| glue|----<---| esp_netif_transmit |--<------| netif_output | |
| | | | | | |
| |---->---| esp_netif_receive |-->------| netif_input | |
| | | | + ----------------+ |
| |....<...| esp_netif_free_rx_buffer |...<.....| packet buffer |
+-----| | | | |
| | | | (D) |
(B) | | (C) | +-----------------------+
--------+ +===========================+
communication NETWORK STACK
DRIVER ESP-NETIF
其中......代表初始化;---->---或---<----代表数据包走向;******代表操作系统的事件调用;|代表用户代码的设置和运行时的配置
配置方法
初始化
- 初始化IO驱动
- 创建一个ESP-NETIF的实例并进行如下配置:
- 特殊属性
- 网络协议栈相关设置
- IO设置
- 将IO驱动句柄和NETIF实例关联
- 配置事件处理函数,至少需要:
- 默认处理函数:用于普通的来自IO驱动器或其他特殊的接口的事件调用
- register处理函数:用故意相关联的应用程序事件调用
运行时配置
- 获取当前TCP/IP设置
- 收取IP事件
- 控制应用程序的FSM
配置的实例
WiFi默认初始化
使用
esp_netif_t *esp_netif_create_default_wifi_ap(void);//初始化wifi为ap模式
esp_netif_t *esp_netif_create_default_wifi_sta(void);//初始化wifi为STA模式
两个API进行默认状态的wifi初始化,函数会返回对应的esp-netif实例
注意:创建的实例如果不再运行时需要停止并释放内存空间,且不能被多次创建
如果需要使用AP+STA模式,两个接口都需要被创建
相关库函数
- 初始化
esp_netif_init(void);//初始化组件
esp_netif_deinit(void);//销毁组件
esp_netif_new(const esp_netif_config_t *esp_netif_config);//根据配置结构体esp_netif_config创建一个新esp-netif实例
esp_netif_destroy(esp_netif_t *esp_netif);//删除一个esp-netif实例
- 配置
esp_netif_set_driver_config(esp_netif_t *esp_netif, const esp_netif_driver_ifconfig_t *driver_config);
//设置与esp-netif对象关联的IO驱动器
esp_netif_attach(esp_netif_t *esp_netif, esp_netif_iodriver_handle driver_handle);
//关联esp-netif对象与IO驱动器
//可以在完成关联后调用处理函数来进行回调或启动驱动器任务
- 使用
esp_netif_receive(esp_netif_t *esp_netif, void *buffer, size_t len, void *eb);
//从应用向TCP/IP协议栈发送包
esp_netif_action_start(void *esp_netif, esp_event_base_t base, int32_t event_id, void *data);
//自动启用TCP/IP协议(使能如DHCP的功能)
esp_netif_action_stop(void *esp_netif, esp_event_base_t base, int32_t event_id, void *data);
//停止向TCP/IP协议栈发送包
esp_netif_action_got_ip(void *esp_netif, esp_event_base_t base, int32_t event_id, void *data);
//获取当前IP
esp_netif_set_mac(esp_netif_t *esp_netif, uint8_t mac[]);
//设置当前MAC地址
esp_netif_set_hostname(esp_netif_t *esp_netif, const char *hostname);
//设置当前主机名
esp_netif_get_hostname(esp_netif_t *esp_netif, const char **hostname);
//获取当前主机名
esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info);
//获取当前IP地址相关信息
esp_netif_set_ip_info(esp_netif_t *esp_netif, const esp_netif_ip_info_t *ip_info);
//设置当前IP地址相关信息
esp_netif_get_netif_impl_index(esp_netif_t *esp_netif);
//获取当前网络接口的代号
esp_netif_dhcps_option(esp_netif_t *esp_netif,
esp_netif_dhcp_option_mode_t opt_op,
esp_netif_dhcp_option_id_t opt_id,
void *opt_val,
uint32_t opt_len);
//配置DHCP服务器
esp_netif_dhcpc_option(esp_netif_t *esp_netif,
esp_netif_dhcp_option_mode_t opt_op,
esp_netif_dhcp_option_id_t opt_id,
void *opt_val,
uint32_t opt_len)
//配置DHCP客户端
esp_netif_dhcpc_start(esp_netif_t *esp_netif);//开启DHCP客户端
esp_netif_dhcpc_stop(esp_netif_t *esp_netif);//停止DHCP客户端
esp_netif_dhcps_start(esp_netif_t *esp_netif);//开启DHCP服务器
esp_netif_dhcps_stop(esp_netif_t *esp_netif);//关闭DHCP服务器
esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status);
//获取当前DHCP客户端状态
esp_netif_dhcps_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status);
//获取当前DHCP服务器状态
esp_netif_set_dns_info(esp_netif_t *esp_netif, esp_netif_dns_type_t type, esp_netif_dns_info_t *dns);
//设置DNS服务器信息
esp_netif_get_dns_info(esp_netif_t *esp_netif, esp_netif_dns_type_t type, esp_netif_dns_info_t *dns);
//获取DNS服务器信息
esp_netif_create_ip6_linklocal(esp_netif_t *esp_netif);//创建本地IPv6地址
esp_netif_set_ip4_addr(esp_ip4_addr_t *addr, uint8_t a, uint8_t b, uint8_t c, uint8_t d);//设置本地IPv4地址
esp_netif_get_ip6_global(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6)//创建全局IPv6地址
esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6);//获取本地IPv6地址
- 事件处理函数
esp_wifi_set_default_wifi_sta_handlers(void);
esp_wifi_set_default_wifi_ap_handlers(void);
- 默认设置
esp_netif_create_default_wifi_ap(void);
esp_netif_create_default_wifi_sta(void);
esp_netif_create_default_wifi_mesh_netifs(esp_netif_t **p_netif_sta, esp_netif_t **p_netif_ap);
HTTP Server组件
HTTP Server 组件提供了在 ESP32 上运行轻量级 Web 服务器的功能
使用步骤:
- 使用httpd_start()创建HTTP Server的实例
API会根据具体配置为其分配内存和资源,该函数返回指向服务器实例的指针(句柄)
服务器使用两个套接字,其中一个用于监听HTTP流量(TCP类型),一个用来处理控制信号(UDP类型),它们在服务器的任务循环中轮流使用。TCP 流量被解析为 HTTP 请求,根据请求的 URI (Uniform Resource Identifier统一资源标志符,表示web上每一种可用的资源)来调用用户注册的处理程序,在处理程序中需要发送回 HTTP 响应数据包。
URI通常由三部分组成:资源的命名机制;存放资源的主机名;资源自身的名称。另外,常说的URL是URI的一个子集(Uniform Resource Locator统一资源定位符),URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,URL的格式由三部分组成:第一部分是协议(或称为服务方式);第二部分是存有该资源的主机IP地址(有时也包括端口号);第三部分是主机资源的具体地址,如目录和文件名等。第一部分和第二部分用“😕/”符号隔开,第二部分和第三部分用“/”符号隔开。第一部分和第二部分是不可缺少的,第三部分有时可以省略。
使用结构体httpd_config_t来配置服务器的各种设定(任务优先级、堆栈大小)
API和结构体如下所示
httpd_start(httpd_handle_t *handle, const httpd_config_t *config);//开启HTTP服务器并分配内存资源
//示例应用
httpd_handle_t start_webserver(void)
{
//可以使用以下宏函数来初始化httpd_config为默认值
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
//设置服务器句柄为空
httpd_handle_t server = NULL;
//启动HTTP服务器
if (httpd_start(&server, &config) == ESP_OK)
{
//注册uri句柄
httpd_register_uri_handler(server, &uri_get);
httpd_register_uri_handler(server, &uri_post);
}
//返回服务器句柄,如果启动失败则句柄为空
return server;
}
typedef struct httpd_config
{
unsigned task_priority;//RTOS服务器任务优先级
size_t stack_size;//服务器任务的最大栈大小
BaseType_t core_id;//服务器任务运行在哪个CPU core上
uint16_t server_port;//服务器使用的端口号
uint16_t ctrl_port;//在服务器组件间异步交换控制信号的UDP端口号
uint16_t max_open_sockets;//最大连接的套接字/客户端数量
uint16_t max_uri_handlers;//最大允许的uri句柄数量
uint16_t max_resp_headers;//最大允许的HTTP响应头
uint16_t backlog_conn;//积压链接的数量
bool lru_purge_enable;//是否清除最近使用的连接
uint16_t recv_wait_timeout;//接收函数的超时时间
uint16_t send_wait_timeout;//发送函数的超时时间
void *global_user_ctx;//全局用户上下文,专门用于存储服务器上下文中的用户数据
httpd_free_ctx_fn_t global_user_ctx_free_fn;//释放空间函数
void *global_transport_ctx;//全局传输上下文,用于存储部分解码或加密使用到的数据,和全局用户上下文意昂需要用free()函数释放内存空间,除非global_transport_ctx_free_fn被指定
httpd_free_ctx_fn_t global_transport_ctx_free_fn;//释放空间函数
httpd_open_func_t open_fn;//自定义session开启回调函数,在新的session开启时被调用
httpd_close_func_t close_fn;//自定义session关闭回调函数
httpd_uri_match_func_t uri_match_fn;//URI匹配函数,用于在搜索到匹配的URI时调用
}httpd_config_t;
- 配置URI处理程序
使用httpd_register_uri_handler()完成
//API原型
esp_err_t httpd_register_uri_handler(httpd_handle_t handle, const httpd_uri_t *uri_handler)
//示例
//URI处理函数
esp_err_t my_uri_handler(httpd_req_t* req)
{
//收取请求、处理并发送响应
....
....
....
//出错状态
if (....)
{
//返回错误
return ESP_FAIL;
}
//成功状态
return ESP_OK;
}
//URI句柄结构体
httpd_uri_t my_uri {
.uri = "/my_uri/path/xyz",//相关的URI
.method = HTTPD_GET,//方法
.handler = my_uri_handler,//URI处理函数
.user_ctx = NULL//指向用户上下文数据的指针
};
//注册句柄
if (httpd_register_uri_handler(server_handle, &my_uri) != ESP_OK)
{
//如果注册失败就....
....
}
通过传入httpd_uri_t结构体类型的对象来注册 URI 处理程序
- 使用httpd_stop()函数停止HTTP服务器
该API会根据传入的句柄停止服务器并释放相关联的内存和资源。这是一个阻塞函数——首先给服务器任务发送停止信号,然后等待其终止。期间服务器任务会关闭所有已打开的连接,删除已注册的 URI 处理程序,并将所有会话的上下文数据重置为空。
esp_err_t httpd_stop(httpd_handle_t handle);//根据传入的服务器句柄停止指向的服务器
可以使用以下的代码来安全地停止服务器
//示例应用
void stop_webserver(httpd_handle_t server)
{
//确保指针非空
if (server != NULL)
{
httpd_stop(server);//停止服务器
}
}
应用实例
/* URI 处理函数,在客户端发起 GET /uri 请求时被调用 */
esp_err_t get_handler(httpd_req_t *req)
{
/* 发送回简单的响应数据包 */
const char[] resp = "URI GET Response";
httpd_resp_send(req, resp, strlen(resp));
return ESP_OK;
}
/* URI 处理函数,在客户端发起 POST /uri 请求时被调用 */
esp_err_t post_handler(httpd_req_t *req)
{
/* 定义 HTTP POST 请求数据的目标缓存区
* httpd_req_recv() 只接收 char* 数据,但也可以是任意二进制数据(需要类型转换)
* 对于字符串数据,null 终止符会被省略,content_len 会给出字符串的长度 */
char[100] content;
/* 如果内容长度大于缓冲区则截断 */
size_t recv_size = MIN(req->content_len, sizeof(content));
int ret = httpd_req_recv(req, content, recv_size);
if (ret <= 0)/* 返回 0 表示连接已关闭 */
{
/* 检查是否超时 */
if (ret == HTTPD_SOCK_ERR_TIMEOUT)
{
/* 如果是超时,可以调用 httpd_req_recv() 重试
* 简单起见,这里我们直接响应 HTTP 408(请求超时)错误给客户端 */
httpd_resp_send_408(req);
}
/* 如果发生了错误,返回 ESP_FAIL 可以确保底层套接字被关闭 */
return ESP_FAIL;
}
/* 发送简单的响应数据包 */
const char[] resp = "URI POST Response";
httpd_resp_send(req, resp, strlen(resp));
return ESP_OK;
}
/* GET /uri 的 URI 处理结构 */
httpd_uri_t uri_get = {
.uri = "/uri",
.method = HTTP_GET,
.handler = get_handler,
.user_ctx = NULL
};
/* POST /uri 的 URI 处理结构 */
httpd_uri_t uri_post = {
.uri = "/uri",
.method = HTTP_POST,
.handler = post_handler,
.user_ctx = NULL
};
/* 启动 Web 服务器的函数 */
httpd_handle_t start_webserver(void)
{
/* 生成默认的配置参数 */
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
/* 置空 esp_http_server 的实例句柄 */
httpd_handle_t server = NULL;
/* 启动 httpd server */
if (httpd_start(&server, &config) == ESP_OK)
{
/* 注册 URI 处理程序 */
httpd_register_uri_handler(server, &uri_get);
httpd_register_uri_handler(server, &uri_post);
}
/* 如果服务器启动失败,返回的句柄是 NULL */
return server;
}
/* 停止 Web 服务器的函数 */
void stop_webserver(httpd_handle_t server)
{
if (server)
{
/* 停止 httpd server */
httpd_stop(server);
}
}
posted on 2021-11-11 23:50 redlightASl 阅读(1285) 评论(0) 编辑 收藏 举报