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

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

码农世界 2024-05-26 前端 76 次浏览 0个评论

准备:

  • 装好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和显示在条框中,并回发给服务器。
          
          
          
              
              
              HTTP Page
          
          
              

          Web Client.

          • 在web_server.h中添加web_server_init()的声明
            #ifndef _WEB_SERVER_H_
            #define _WEB_SERVER_H_
            void web_server_init(void);
            #endif
            
            • 在web_server.c中添加web server函数主体
              #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)
              {
                  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的底层架子,如何玩出花来还望各位看官。

转载请注明来自码农世界,本文标题:《手把手教你使用VScode+ESP-IDF在ESP32上搭建web server,并作为web socket server进行数据交互》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,76人围观)参与讨论

还没有评论,来说两句吧...

Top