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

其中......代表初始化;---->---或---<----代表數據包走向;******代表操作系統的事件調用;|代表用戶代碼的設置和運行時的配置

配置方法

初始化

  1. 初始化IO驅動
  2. 創建一個ESP-NETIF的實例並進行如下配置:
    1. 特殊屬性
    2. 網絡協議棧相關設置
    3. IO設置
  3. 將IO驅動句柄和NETIF實例關聯
  4. 配置事件處理函數,至少需要:
    1. 默認處理函數:用於普通的來自IO驅動器或其他特殊的接口的事件調用
    2. register處理函數:用故意相關聯的應用程序事件調用

運行時配置

  1. 獲取當前TCP/IP設置
  2. 收取IP事件
  3. 控制應用程序的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模式,兩個接口都需要被創建

相關庫函數

  1. 初始化
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實例
  1. 配置
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驅動器
//可以在完成關聯后調用處理函數來進行回調或啟動驅動器任務
  1. 使用
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地址
  1. 事件處理函數
esp_wifi_set_default_wifi_sta_handlers(void);
esp_wifi_set_default_wifi_ap_handlers(void);
  1. 默認設置
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組件

原文地址:https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.1/api-reference/protocols/esp_http_server.html

HTTP Server 組件提供了在 ESP32 上運行輕量級 Web 服務器的功能

使用步驟:

  1. 使用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;
  1. 配置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 處理程序

  1. 使用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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM