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);
}
}