ESPIDF开发ESP32学习笔记【HTTP客户端实现】
TCP协议栈
ESP使⽤lwIP作为嵌⼊式的TCP/IP协议栈⽀持
lwIP是⼀套在MCU层级上⽤C实现的IP协议栈,可以运⾏在裸机/RTOS/嵌⼊式Linux,乐鑫为ESP32提供了相关移植包
相关内容可以参考lwIP库函数,在LWIP和ESP-NETIF组件中得到⽀持
esp_err_t esp_netif_init(void);
esp_err_t esp_netif_deinit(void);
esp_netif_t *esp_netif_new(const esp_netif_config_t *esp_netif_config);
void esp_netif_destroy(esp_netif_t *esp_netif);
esp_err_t esp_netif_set_driver_config(esp_netif_t *esp_netif,const esp_netif_driver_ifconfig_t *driver_config);
esp_err_t esp_netif_attach(esp_netif_t *esp_netif, esp_netif_iodriver_handle driver_handle);
esp_err_t esp_netif_receive(esp_netif_t *esp_netif,void*buffer, size_t len,void*eb);
void esp_netif_action_start(void*esp_netif, esp_event_base_t base, int32_t event_id,void*data);
void esp_netif_action_stop(void*esp_netif, esp_event_base_t base, int32_t event_id,void*data);
void esp_netif_action_connected(void*esp_netif, esp_event_base_t base, int32_t event_id,void*data);
void esp_netif_action_disconnected(void*esp_netif, esp_event_base_t base, int32_t event_id,void*data);
void esp_netif_action_got_ip(void*esp_netif, esp_event_base_t base, int32_t event_id,void*data);
esp_err_t esp_netif_set_mac(esp_netif_t *esp_netif, uint8_t mac[]);
esp_err_t esp_netif_set_hostname(esp_netif_t *esp_netif,const char*hostname);
esp_err_t esp_netif_get_hostname(esp_netif_t *esp_netif,const char**hostname);
bool esp_netif_is_netif_up(esp_netif_t *esp_netif);
esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info);
esp_err_t esp_netif_get_old_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info);
esp_err_t esp_netif_set_ip_info(esp_netif_t *esp_netif,const esp_netif_ip_info_t *ip_info);
esp_err_t esp_netif_set_old_ip_info(esp_netif_t *esp_netif,const esp_netif_ip_info_t *ip_info);
int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif);
esp_err_t 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);
esp_err_t 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);
esp_err_t esp_netif_dhcpc_start(esp_netif_t *esp_netif);
esp_err_t esp_netif_dhcpc_stop(esp_netif_t *esp_netif);
esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status);
esp_err_t esp_netif_dhcps_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status);
esp_err_t esp_netif_dhcps_start(esp_netif_t *esp_netif);
esp_err_t esp_netif_dhcps_stop(esp_netif_t *esp_netif);
esp_netif组件建⽴在lwip基础上,如上⾯的API所⽰,实现了
TCP/IP协议初始化与内存分配
建⽴基于IP协议的通信
控制本机IP地址与查⽬标IP
DHCP功能
收发TCP报⽂的底层实现
注意:这个组件并没有实现DNS功能,需要使⽤单独的DNS组件才能实现DNS服务器/DNS解析功能
HTTP客户端
ESP-IDF提供了可以实现稳定链接的HTTP-Client组件<esp_http_client>,实现从ESP-IDF应⽤程序发出HTTP/S请求的API
HTTP-Client可以理解成为⼀个没有画⾯的“浏览器”——它与服务器建⽴TCP/IP连接,并收发符合HTTP协议标准的TCP报⽂,其中包含消息头和数据包,数据包会以json格式传输
综上我们可以知道,如果要在ESP-IDF设备和HTTP⽹站(服务器)之间建⽴稳定的连接,需要五个组件:
wifi或ethernet组件,提供底层联⽹功能
lwip组件,提供IP协议的MCU实现
netif组件,提供TCP协议的MCU实现
esp_http_client组件,提供HTTP-Client/Server数据解析和连接处理的实现,其中HTTP-Server组件在之前的博⽂中已经介绍过cJSON组件,⽤于解析服务器回传的json数据/处理本地数据为json格式并POST到服务器
如果有必要,还需要使⽤freertos组件以⽅便多任务处理
使⽤HTTP-Client相关API的步骤如下:
在开始之前,需要先建⽴NVS存储、连接WiFi、初始化netif⽹络接⼝
浏览器json格式化
esp_err_t ret =nvs_flash_init();
if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret =nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(internet_connect());//连接wifi或ethernet
1. esp_http_client_init()
创建⼀个esp_http_client_config_t的实例(实例化对象),并配置HTTP-Client句柄
esp_http_client_config_t config ={
.host = WEB_SERVER,
.path = WEB_PATH_GET_TIME,
.query = WEB_QUERY,
.transport_type = HTTP_TRANSPORT_OVER_TCP,
.event_handler = _http_event_handler,
.user_data = local_response_buffer
};
2. 执⾏HTTP客户端的各种操作
包括打开链接、进⾏数据交换、抑或是关闭链接
所有这些操作都可以通过封装好的函数配合上⾯步骤中指定的event_handler回调函数进⾏实现
esp_http_client_handle_t client =esp_http_client_init(&config);
其中event_handler是基于状态机的,如下所⽰
esp_err_t _http_event_handler(esp_http_client_event_t* evt)
{
static char* output_buffer;// Buffer to store response of http request from event handler
static int output_len;// Stores number of bytes read
switch(evt->event_id)
{
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG,"HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG,"HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG,"HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG,"HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG,"HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
/*
*  Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.        *  However, event handler can also be used in case chunked encoding is used.
*/
if(!esp_http_client_is_chunked_response(evt->client))
{
// If user_data buffer is configured, copy the response into the buffer
if(evt->user_data)
{
memcpy(evt->user_data + output_len, evt->data, evt->data_len);
}
else
{
if(output_buffer ==NULL)
{
output_buffer =(char*)malloc(esp_http_client_get_content_length(evt->client));
output_len =0;
if(output_buffer ==NULL)
{
ESP_LOGE(TAG,"Failed to allocate memory for output buffer");
return ESP_FAIL;
}
}
memcpy(output_buffer + output_len, evt->data, evt->data_len);
}
output_len += evt->data_len;
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG,"HTTP_EVENT_ON_FINISH");
if(output_buffer !=NULL)
{
// Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
// ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
free(output_buffer);
output_buffer =NULL;
output_len =0;
}
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG,"HTTP_EVENT_DISCONNECTED");
int mbedtls_err =0;
esp_err_t err =esp_tls_get_and_clear_last_error(evt->data,&mbedtls_err,NULL);
if(err !=0)
{
if(output_buffer !=NULL)
{
free(output_buffer);
output_buffer =NULL;
output_len =0;
}
ESP_LOGI(TAG,"Last esp error code: 0x%x", err);
ESP_LOGI(TAG,"Last mbedtls failure: 0x%x", mbedtls_err);
}
break;
}
return ESP_OK;
}
3. 通过esp_http_client_cleanup()关闭链接,并释放系统资源
需要注意:这个函数必须是操作完成后调⽤的最后⼀个函数
需要注意⼀点:esp_http_client_init()建⽴的连接是持久性的,因此HTTP客户端可以在多个交换中重⽤相同的连接,只要服务器没有使⽤报头connection: close强⾏关闭,或者没有使*⽤esp_http_client_cleanup()*关闭链接,设备的HTTP链接就会保持打开状态
常⽤的HTTP-Client操作
HTTP:GET
// GET
esp_err_t err =esp_http_client_perform(client);
if(err == ESP_OK)
{
ESP_LOGI(TAG,"HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
}
else
{
ESP_LOGE(TAG,"HTTP GET request failed: %s",esp_err_to_name(err));
}
ESP_LOG_BUFFER_HEX(TAG, local_response_buffer,strlen(local_response_buffer));
//数据会保存到之前注册在http-client对象中的数据缓存区中
这⾥展⽰了两个常⽤的API
esp_http_client_get_status_code(client)//获取HTTP返回状态码
esp_http_client_get_content_length(client)//获取HTTP返回数据的长度
其中esp_http_client_get_content_length()⽐较特殊,仅能应⽤于返回数据长度定长的状态下,也就是HTTP报头中不能有chunked情况,如果想要接收⾮定长数据,需要使⽤专⽤的API:esp_http_client_is_chunked_response(esp_http_client_handle_t client)先获取报⽂长度,再针对这个长度在回调函数中接收数据到HTTP报⽂缓存区
GitHub上的ESP-IDF repo中热⼼⽹友给出了下⾯的代码来实现简单快捷的request
int http_request(char*http_response,int*http_response_length,int range_start,int range_end,int client_id)
{
int err =-1;
char range[64];
int len_received =0;
if(range_end > range_start)
{
sprintf(range,"bytes=%d-%d", range_start, range_end);
esp_http_client_set_header(ota_client[client_id],"Range", range);
}
err =esp_http_client_open(ota_client[client_id],0);
if(err != ESP_OK)
{
ESP_LOGE(LOG_TAG,"Failed to open HTTP connection: %s",esp_err_to_name(err));
}
else
{
int content_length =esp_http_client_fetch_headers(ota_client[client_id]);
if(content_length <0)
{
ESP_LOGE(LOG_TAG,"HTTP client fetch headers failed");
}
else
{
len_received =esp_http_client_read_response(ota_client[client_id], http_response,*http_response_length); if(len_received >=0)
{
ESP_LOGI(LOG_TAG,"HTTP Status = %d, content_length = %lld",
esp_http_client_get_status_code(ota_client[client_id]),
esp_http_client_get_content_length(ota_client[client_id]));
}
else
{
ESP_LOGE(LOG_TAG,"Failed to read response");
}
}
}
esp_http_client_close(ota_client[client_id]);
*http_response_length = len_received;
return(err);
}
这段代码是基于esp_http_client_open的,速度也相对较快,推荐使⽤
HTTP:POST
如下⾯⽰例:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。