ESP32 IDF开发 应用篇⑰ http请求
别迷路-导航栏
快速导航找到你想要的(文章目录)
此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。
1、博主写这篇技术文章的目的:
(1)、了解 什么是http;
(2)、http的API的使用方法;
(2)、http的编程方法;
2、概述
在前面章节我们已经说过http是基于tcp以上的一种传输协议:
(1)、http协议的定义:
http(Hypertext transfer protocol)超文本传输协议,通过浏览器和服务器进行数据交互,进行超文本(文本、图片、视频等)传输的规定。也就是说,http协议规定了超文本传输所要遵守的规则。
(2)、HTTP协议的特点:
·无状态:HTTP协议是无状态。对于事务处理没有记忆能力,如果需要处理前面信息,则必须重传,这样可能导致每次连接传送的数据量增大
·基于TCP协议:HTTP协议目的是规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节。底层是基于TCP实现的。现在使用的版本当中是默认持久连接的,也就是多次HTTP请求使用一个TCP连接。
·简单快速:客户端向服务端请求服务时,只需要传送请求方法和路径。HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度快;
3、HTTP请求
客户端发送一个HTTP请求到服务器,请求信息包括以下格式:
· 请求行(request line)
· 请求首部(header)
· 请求实体数据 (request body)
4、HTTP响应
HTTP/1.1 200 OK
Date: Tue, 12 Jul 2016 21:36:12 GMT
Content-Length: 563
Content-Type: text/html
6、HTTP的响应状态码
状态码是用来告知客户端服务器端处理请求的结果。凭借状态码用户可以知道服务器是请求处理成功、失败或者是被转发;这样出现了错误也好定位。状态码是由3位数字加原因短语组成。3位数字中的第一位是用来指定状态的类别。共有5种。
HTTP状态码一共有60多种,但是不用全部都记住,因为大部分在工作当中是不经常使用的。经常使用的大概就是16种,下面来详细介绍。(其实最最常用的也就8种,下面有背景色的就是)
1. 200:OK
这个没有什么好说的,是代表请求被正常的处理成功了。
2. 204:No Content
请求处理成功,但是没有数据实体返回,也不允许有实体返回。比如说HEAD请求,可能就会返回204 No Content,因为HEAD就是只获取头信息。这里简单提一下205 Reset Content,和204 No Content的区别是不但没有数据实体返回,而且还需要重置表单,方便用户再次输入。
3. 206:Partial Content
这是客户端使用Content-Range指定了需要的实体数据的范围,然后服务端处理请求成功之后返回用户需要的这一部分数据而不是全部,执行的请求就是GET。返回码就是206:Partial Content。
4. 301 : Moved Permanently
代表永久性定向。该状态码表示请求的资源已经被分配了新的URL,以后应该使用资源现在指定的URL。也就是说如果已经把资源对应的URL保存为书签了,这是应该按照Location首部字段提示的URL重新保存。
5. 302:Found
代表临时重定向。该状态码表示请求的资源已经被分配了新的URL,但是和301的区别是302代表的不是永久性的移动,只是临时的。就是说这个URL还可能会发生改变。如果保存成书签了也不会更新。
6. 303:See Other
和302的区别是303明确规定客户端应当使用GET方法。
7. 304:Not Modified
该状态码表示客户端发送附带条件请求时,服务器端允许请求访问资源,但是没有满足条件。304状态码返回时不包含任何数据实体。304虽然被划分在3XX中但是和重定向没有关系。
8. 307:Temporary Redirect
临时重定向,与302 Found相同,但是302会把POST改成GET,而307就不会。
9. 400:Bad Request
400表示请求报文中存在语法错误。需要修改后再次发送。
10. 401:Unauthorized
该状态码表示发送的请求需要有通过HTTP认证的认证信息。
11. 403:Forbidden
表明请求访问的资源被拒绝了。没有获得服务器的访问权限,IP被禁止等。
12. 404:Not Found
表明请求的资源在服务器上找不到。当然也可以在服务器拒绝请求且不想说明理由时使用。
13. 408:Request Timeout
表示客户端请求超时,就是在客户端和服务器建立连接后服务器在一定时间内没有收到客户端的请求。
14. 500:Internal Server Error
表明服务器端在执行请求时发生了错误,很有可能是服务端程序的Bug或者临时故障。
15. 503:Service Unavailable
表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入Retry-After字段再返回给客户端。
16. 504:Getaway Timeout
网关超时,是代理服务器等待应用服务器响应时的超时,和408 Request Timeout的却别就是504是服务器的原因而不是客户端的原因
7、相关 API的介绍
(1)、域名解析
/*
*解析服务位置的名称(例如,主机名)和/或
*服务名称,并返回一组套接字地址和相关的
*用于创建用于寻址的套接字的信息
* @param nodename 主机的描述性名称或地址字符串
* (为 NULL -> local address本机地址)
* @param servname 端口号为NULL字符串
* @param hints structure 包含用于设置socktype和协议的输入值
* @param res 指向存储结果的指针的指针(失败时设置为NULL)
* @return 成功返回0,失败返回非零
* */
Int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
(2)、创建socket,百度socket函数
int socket(int family, int type, int protocol);
family指定协议族;type参数指定socket的类型:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;protocol通常赋值"0"。 socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
(3)、连接服务connect
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
sockfd是目的服务器的sockt描述符;serv_addr是服务器端的IP地址和端口号的地址;addrlen通常为sizeof(struct sockaddr_in);
8、软件设计
软件是基于idf_wifi_sta.c原代码基础上做的修改
在连接好wifi之后开始执行http任务即可:
①、解析DNS域名
首先填充addrinfo
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
定义Socket 地址指针从函数中获得Socket 地址
getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
struct addrinfo {
int ai_flags; /* Input flags. */
int ai_family; /* Address family of socket. */
int ai_socktype; /* Socket type. */
int ai_protocol; /* Protocol of socket. */
socklen_t ai_addrlen; /* Length of socket address. */
struct sockaddr *ai_addr; /* Socket address of socket. */
char *ai_canonname; /* Canonical name of service location. */
struct addrinfo *ai_next; /* Pointer to next in list. */
};
从域名中解析到IP地址之后
②、创建并连接Socket
s = socket(res->ai_family, res->ai_socktype, 0);
connect(s, res->ai_addr, res->ai_addrlen)
③、读写http包
write(s, REQUEST, strlen(REQUEST)
read(s, recv_buf, sizeof(recv_buf)-1)
9、实例
复制idf_wifi sta工程改名字为 idf_wifi_http(因为本例程是基于idf_wifi sta)即可 文件名字改为 idf_wifi_http.c makefile文件也改成 PROJECT_NAME := idf_wifi_http即可,然后复制一下代码测试。
/**********************************************************************
* 文件名: idf_wifi_http.c
* 创建人:
* 创建日期:
* 修改人:
* 修改日期:
* 版本号: V1.1
* 备注:
* 公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
static const char *TAG = "WIFI_HTTP";
#define WEB_SERVER "www.elecfans.com"
#define WEB_PORT "80"
#define WEB_URL "/tags/"
//http请求包
static const char *REQUEST = "GET "WEB_URL" HTTP/1.1\r\n"
"Host: "WEB_SERVER"\r\n"
"Connection: close\r\n"
"\r\n";
//事件标志组
static EventGroupHandle_t xCreatedEventGroup_WifiConnect = NULL;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int retry_num = 0;
#define HttpGet_TASK_STACK_SIZE 8192
#define HttpGet_TASK_PRIO 2
//任务句柄
static TaskHandle_t xHandleTaskHttpGet = NULL;
static void vTaskHttpGet(void *pvParameters);
/***********************************************************************
* 函数:
* 描述: wifi 回调函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
ESP_LOGI(TAG, "event_base: %s ; event_id : %d",event_base,event_id);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();//开始连接
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) //wifi断开时
{
if (retry_num < CONFIG_ESP_MAXIMUM_RETRY) //重新连接次数
{
esp_wifi_connect();
retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
}
else
{
xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)//获得IP
{
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
retry_num = 0;
xEventGroupSetBits(xCreatedEventGroup_WifiConnect, WIFI_CONNECTED_BIT);
xEventGroupClearBits(xCreatedEventGroup_WifiConnect, WIFI_FAIL_BIT);
}
}
/***********************************************************************
* 函数:
* 描述: wifi sta初始化代码相对比较固定,一般不需要做修改
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void wifi_init_sta(void)
{
ESP_ERROR_CHECK(esp_netif_init());//初始化TCP / IP堆栈和esp-netif
ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建默认event loop
esp_netif_create_default_wifi_sta();//创建默认的WIFI STA。
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//给wifi_init_config_t结构体初始化系统默认的数据
ESP_ERROR_CHECK(esp_wifi_init(&cfg));//使用默认的参数初始化wifi
//注册wifi 连接过程中回调函数
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
ESP_LOGI(TAG, "WIFI_EVENT :%s",WIFI_EVENT);
ESP_LOGI(TAG, "IP_EVENT :%s",IP_EVENT);
//设置账户和密码,在menuconfig中设置
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD
//.ssid = "ssid",
//.password = "password"
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );//设置为sta模式
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );//启动
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
/***********************************************************************
* 函数:
* 描述: 主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main()
{
//初始化NVS
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_LOGI(TAG, "ESP_WIFI_MODE_STA");
//创建事件标志组,用于等待wifi连接
xCreatedEventGroup_WifiConnect = xEventGroupCreate();
//WIFI sta初始化
wifi_init_sta();
xTaskCreate(vTaskHttpGet, // 任务函数
"vTaskHttpGet", // 任务名
HttpGet_TASK_STACK_SIZE, // 任务栈大小,单位 word,也就是 4 字节
NULL, // 任务参数
HttpGet_TASK_PRIO, // 任务优先级
&xHandleTaskHttpGet);
}
static void vTaskHttpGet(void *pvParameters)
{
//等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄
WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置
pdFALSE, //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
pdTRUE, //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
portMAX_DELAY); //等待延迟时间,一直等待
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
struct in_addr *addr;
int s, r;
char recv_buf[64];
for (;;)
{
//DNS域名解析
int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
if(err != 0 || res == NULL)
{
ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p\r\n", err, res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
//打印获取的IP
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
//创建socket
s = socket(res->ai_family, res->ai_socktype, 0);
if(s < 0)
{
ESP_LOGE(TAG, "... Failed to allocate socket.\r\n");
close(s);
freeaddrinfo(res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
//连接ip
if(connect(s, res->ai_addr, res->ai_addrlen) != 0)
{
ESP_LOGE(TAG, "... socket connect failed errno=%d\r\n", errno);
close(s);
freeaddrinfo(res);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
freeaddrinfo(res);
//发送http包
if (write(s, REQUEST, strlen(REQUEST)) < 0)
{
ESP_LOGE(TAG, "... socket send failed\r\n");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... socket send success");
//清缓存
memset(recv_buf,0,sizeof(recv_buf));
/* Read HTTP response */
do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1);
for(int i = 0; i < r; i++) {
putchar(recv_buf[i]);
}
} while(r > 0);
close(s);
for(int countdown = 20; countdown >= 0; countdown--)
{
ESP_LOGI(TAG, "%d... ", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGI(TAG, "Starting again!");
}
}
10、调试结果
浏览器上大部分都是https相关网站,找了个链接http://www.elecfans.com/tags/
汉字显示不出来所以出现乱码
在浏览器上按F12打开调试窗口对比接收到的数据
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092