手把手教你使用VScode+ESP-IDF在ESP32上搭建web server,并作为web socket server进行数据交互

烟雨迷城 2024-06-14 09:03:05 阅读 62

准备:

装好ESP-IDF插件的VScode;ESP32开发板(ESP32-S2、ESP32-S3都行)。

步骤:

打开VScode,按F1,输入Show Examples Projects后,搜索station,创建station例程。这是一个添加ssid和密码后就能连接无线网络的例程。

在这里插入图片描述

打开工程,修改要连接热点的SSID与PASS

在这里插入图片描述

点击menuconfig后搜索“websocket”,勾选“WebSocket server support”以启用web socket,保存,退出。

在这里插入图片描述

在这里插入图片描述

- 修改Max HTTP Request Header Length为2048,以保证HTTP的报头发出不报错。

在这里插入图片描述

编译,并烧录进ESP32开发板中,以验证基础工程的正确性。可以看到被路由器DHCP分配到的IP为192.168.31.117。

在这里插入图片描述

在左侧main目录下创建 web_server.c、web_server.h、web_client.html。添加这些文件到mian目录下“CMakeLists.txt”中,其中web_client.html的路径添加为EMBED_FILES,如果设计的页面有图片,图片路径也要添加其中,用空格隔开。

在这里插入图片描述

idf_component_register(SRCS "station_example_main.c" "web_server.c" INCLUDE_DIRS "." EMBED_FILES "web_client.html" )web_client.html中随便添些HTML代码,设计了一个简单的页面,寻到web_client.html文件存放目录,双击运行。若只是在修改页面效果,可将客户端连接的地址固定,在VScode修改保存后在浏览器中按F5刷新,能直接看到最新设计效果。用JavaScript语言,创建客户端套接字“ws_client”,JavaScript代码添加在HTML代码的下方。此处设计的功能为:将从web server收到的字符串数据打印log和显示在条框中,并回发给服务器。此脚本嵌入在ESP32的flash后,在使用时,将会在被HTTP客户端请求时发出去。

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HTTP Page</title></head><body> <h1>Web Client.</h1> <p style="display: inline;"><label for="textID">收到数据:</label></p> <input type="text" id="textID" value=""></body></html><script>//服务器地址 //烧录进ESP32时使用 "ws://"+window.location.host+"/ws" //调试html时直接写 "ws://192.168.31.117/ws"const ws_client = new WebSocket("ws://"+window.location.host+"/ws");/*ws_client连接成功事件*/ws_client.onopen = function (event) { };/*ws_client错误事件*/ws_client.onerror = function(error) { };/*ws_client接收数据*/ws_client.onmessage = function (event) { data_processing(event.data); //获取数据交给别的函数处理};/*数据处理*/function data_processing(data){ console.log(data); //打印在调试框 document.getElementById("textID").value = data; //显示在ID为"textID"的条框中 ws_client.send(data); //发给服务器}</script>

在这里插入图片描述

在这里插入图片描述

web_server.h中添加web_server_init()的声明

#ifndef _WEB_SERVER_H_#define _WEB_SERVER_H_void web_server_init(void);#endifweb_server.c中添加web server函数主体。函数主要包括web server初始化,websocket客户端管理,websocket数据接收回调函数,websocket数据接收处理任务,uri注册回调函数,http时间处理,websocket数据发送函数。说明全都在注释里。

#include "web_server.h"#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "freertos/queue.h"#include "esp_http_server.h"#define BUFFER_LEN 1024 typedef struct { char data[BUFFER_LEN]; int len; int client_socket;}DATA_PARCEL;static httpd_handle_t web_server_handle = NULL;//ws服务器唯一句柄static QueueHandle_t ws_server_rece_queue = NULL;//收到的消息传给任务处理static QueueHandle_t ws_server_send_queue = NULL;//异步发送队列/*此处只是管理ws socket server发送时的对象,以确保多客户端连接的时候都能收到数据,并不能限制HTTP请求*/#define WS_CLIENT_QUANTITY_ASTRICT 5 //客户端数量static int WS_CLIENT_LIST[WS_CLIENT_QUANTITY_ASTRICT];//客户端套接字列表static int WS_CLIENT_NUM = 0; //实际连接数量/*客户端列表 记录客户端套接字*/static void ws_client_list_add(int socket){ /*检查是否超出限制*/ if (WS_CLIENT_NUM>=WS_CLIENT_QUANTITY_ASTRICT) { return; } /*检查是否重复*/ for (size_t i = 0; i < WS_CLIENT_QUANTITY_ASTRICT; i++) { if (WS_CLIENT_LIST[i] == socket) { return; } } /*添加套接字至列表中*/ for (size_t i = 0; i < WS_CLIENT_QUANTITY_ASTRICT; i++) { if (WS_CLIENT_LIST[i] <= 0){ WS_CLIENT_LIST[i] = socket; //获取返回信息的客户端套接字 printf("ws_client_list_add:%d\r\n",socket); WS_CLIENT_NUM++; return; } }}/*客户端列表 删除客户端套接字*/static void ws_client_list_delete(int socket){ for (size_t i = 0; i < WS_CLIENT_QUANTITY_ASTRICT; i++) { if (WS_CLIENT_LIST[i] == socket) { WS_CLIENT_LIST[i] = 0; printf("ws_client_list_delete:%d\r\n",socket); WS_CLIENT_NUM--; if (WS_CLIENT_NUM<0) { WS_CLIENT_NUM = 0; } break; } }}/*ws服务器接收数据*/static DATA_PARCEL ws_rece_parcel; static esp_err_t ws_server_rece_data(httpd_req_t *req){ if (req->method == HTTP_GET) { ws_client_list_add(httpd_req_to_sockfd(req)); return ESP_OK; } esp_err_t ret = ESP_FAIL; httpd_ws_frame_t ws_pkt; memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); memset(&ws_rece_parcel, 0, sizeof(DATA_PARCEL)); ws_pkt.type = HTTPD_WS_TYPE_TEXT; ws_pkt.payload = (uint8_t*)ws_rece_parcel.data; //指向缓存区 ret = httpd_ws_recv_frame(req, &ws_pkt, 0);//设置参数max_len = 0来获取帧长度 if (ret != ESP_OK) { printf("ws_server_rece_data data receiving failure!"); return ret; } if (ws_pkt.len>0) { ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);/*设置参数max_len 为 ws_pkt.len以获取帧有效负载 */ if (ret != ESP_OK) { printf("ws_server_rece_data data receiving failure!"); return ret; } ws_rece_parcel.len = ws_pkt.len; ws_rece_parcel.client_socket = httpd_req_to_sockfd(req); if (xQueueSend(ws_server_rece_queue ,&ws_rece_parcel,pdMS_TO_TICKS(1))){ ret = ESP_OK; } } else { printf("ws_pkt.len<=0"); } return ret;}/*WEB SOCKET*/static const httpd_uri_t ws = { .uri = "/ws", .method = HTTP_GET, .handler = ws_server_rece_data, .user_ctx = NULL, .is_websocket = true};/*首页HTML GET处理程序 */static esp_err_t home_get_handler(httpd_req_t *req){ /*获取脚本web_client.html的存放地址和大小,接受http请求时将脚本发出去*/ extern const unsigned char upload_script_start[] asm("_binary_web_client_html_start");/*web_client.html文件在bin中的位置*/ extern const unsigned char upload_script_end[] asm("_binary_web_client_html_end"); const size_t upload_script_size = (upload_script_end - upload_script_start); httpd_resp_send(req, (const char *)upload_script_start, upload_script_size); return ESP_OK;}/*首页HTML*/static const httpd_uri_t home = { .uri = "/", .method = HTTP_GET, .handler = home_get_handler, .user_ctx = NULL};/*http事件处理*/static void ws_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data){ if (event_base == ESP_HTTP_SERVER_EVENT ) { switch (event_id) { case HTTP_SERVER_EVENT_ERROR ://当执行过程中出现错误时,将发生此事件 break; case HTTP_SERVER_EVENT_START ://此事件在HTTP服务器启动时发生 break; case HTTP_SERVER_EVENT_ON_CONNECTED ://一旦HTTP服务器连接到客户端,就不会执行任何数据交换 break; case HTTP_SERVER_EVENT_ON_HEADER ://在接收从客户端发送的每个报头时发生 break; case HTTP_SERVER_EVENT_HEADERS_SENT ://在将所有标头发送到客户端之后 break; case HTTP_SERVER_EVENT_ON_DATA ://从客户端接收数据时发生 break; case HTTP_SERVER_EVENT_SENT_DATA ://当ESP HTTP服务器会话结束时发生 break; case HTTP_SERVER_EVENT_DISCONNECTED ://连接已断开 esp_http_server_event_data* event = (esp_http_server_event_data*)event_data; ws_client_list_delete(event->fd); break; case HTTP_SERVER_EVENT_STOP ://当HTTP服务器停止时发生此事件 break; } }}/*异步发送函数,将其放入HTTPD工作队列*/static DATA_PARCEL async_buffer;static void ws_async_send(void *arg){ if (xQueueReceive(ws_server_send_queue,&async_buffer,0)) { httpd_ws_frame_t ws_pkt ={ 0}; ws_pkt.payload = (uint8_t*)async_buffer.data; ws_pkt.len = async_buffer.len; ws_pkt.type = HTTPD_WS_TYPE_TEXT; httpd_ws_send_frame_async(web_server_handle, async_buffer.client_socket, &ws_pkt) ; }}/*ws 发送函数*/static DATA_PARCEL send_buffer;static void ws_server_send(const char * data ,uint32_t len , int client_socket){ memset(&send_buffer,0,sizeof(send_buffer)); send_buffer.client_socket = client_socket; send_buffer.len = len; memcpy(send_buffer.data,data,len); xQueueSend(ws_server_send_queue ,&send_buffer,pdMS_TO_TICKS(1)); httpd_queue_work(web_server_handle, ws_async_send, NULL);//进入排队}/*数据发送任务,每隔一秒发送一次*/static void ws_server_send_task(void *p){ uint32_t task_count = 0; char buf[50] ; while (1) { memset(buf,0,sizeof(buf)); sprintf(buf,"Hello World! %ld",task_count); for (size_t i = 0; i < WS_CLIENT_QUANTITY_ASTRICT; i++) { if (WS_CLIENT_LIST[i]>0) { ws_server_send(buf,strlen(buf),WS_CLIENT_LIST[i]); } } task_count++; vTaskDelay(pdMS_TO_TICKS(1000)); }}/*数据接收处理任务*/static DATA_PARCEL rece_buffer; static void ws_server_rece_task(void *p){ while (1) { if(xQueueReceive(ws_server_rece_queue,&rece_buffer,portMAX_DELAY)) { printf("socket : %d\tdata len : %d\tpayload : %s\r\n",rece_buffer.client_socket,rece_buffer.len,rece_buffer.data); } }}/*web服务器初始化*/void web_server_init(void){ httpd_config_t config = HTTPD_DEFAULT_CONFIG(); // 启动httpd服务器 if (httpd_start(&web_server_handle, &config) == ESP_OK) { printf("web_server_start\r\n"); } else { printf("Error starting ws server!"); } esp_event_handler_instance_register(ESP_HTTP_SERVER_EVENT,ESP_EVENT_ANY_ID, &ws_event_handler,NULL,NULL);//注册处理程序 httpd_register_uri_handler(web_server_handle, &home);//注册uri处理程序 httpd_register_uri_handler(web_server_handle, &ws);//注册uri处理程序 /*创建接收队列*/ ws_server_rece_queue = xQueueCreate( 3 , sizeof(DATA_PARCEL)); if (ws_server_rece_queue == NULL ) { printf("ws_server_rece_queue ERROR\r\n"); } /*创建发送队列*/ ws_server_send_queue = xQueueCreate( 3 , sizeof(DATA_PARCEL)); if (ws_server_send_queue == NULL ) { printf("ws_server_send_queue ERROR\r\n"); } BaseType_t xReturn ; /*创建接收处理任务*/ xReturn = xTaskCreatePinnedToCore(ws_server_rece_task,"ws_server_rece_task",4096,NULL,15,NULL, tskNO_AFFINITY); if(xReturn != pdPASS) { printf("xTaskCreatePinnedToCore ws_server_rece_task error!\r\n"); } /*创建发送任务,此任务不是必须的,因为发送函数可以放在任意地方*/ xReturn = xTaskCreatePinnedToCore(ws_server_send_task,"ws_server_send_task",4096,NULL,15,NULL, tskNO_AFFINITY); if(xReturn != pdPASS) { printf("xTaskCreatePinnedToCore ws_server_send_task error!\r\n"); }} 将web_server_init()添加到app_main()中

在这里插入图片描述

编译,烧录到ESP32中,电脑与ESP32连接同一个局域网,在电脑浏览器中输入路由器给ESP32分配的IP:192.168.31.117,回车。

在这里插入图片描述

资源获取

免费下载链接:ESP32_web_server.zip

结束语

HTTP协议的服务器不能主动发消息客户端,但web socket协议可以。这教程只是搭一个web server的底层架子,如何玩出花来还望各位看官。

如果对你有用,请点个赞吧!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。