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

Hello http! 简单来说响应报文由状态行、响应首部字段(响应头)、响应实体组成,其中第一行是状态行,依次包含HTTP版本,状态码和状态短语组成;在一个回车换行之后是响应头,也是键值对的形式,字段名:值;然后会有一个空行也包含回车换行,之后是响应实体,就是要传输的数据。在上面的例子当中就是一个非常简单的HTML页面。对于响应状态码,首部字段键值对稍后会有更加详细的说明。 ## 5、HTTP请求方法   请求方法是客户端用来告知服务器其动作意图的方法。就像下达命令一样。在HTTP1.1版本中支持GET、POST等近10种方法。需要注意的是方法名区分大小写,需要用大写字母。下面详细说明。   **1.GET:获取资源**    GET方法用来请求访问已被URI识别的资源。也就是指定了服务器处理请求之后响应的内容。   **2.POST:传输实体主体**   POST方法用来传输实体主体。在url后面添加链接。   **3.PUT:传输文件**    PUT方法用来传输文件。类似FTP协议,文件内容包含在请求报文的实体中,然后请求保存到URL指定的服务器位置。   **4.HEAD:获得报文首部**    HEAD方法类似GET方法,但是不同的是HEAD方法不要求返回数据。用于确认URI的有效性及资源更新时间等。   **5.DELETE:删除文件**    DELETE方法用来删除文件,是与PUT相反的方法。DELETE是要求返回URL指定的资源。   **6.OPTIONS:询问支持的方法**    因为并不是所有的服务器都支持规定的方法,为了安全有些服务器可能会禁止掉一些方法例如DELETE、PUT等。那么OPTIONS就是用来询问服务器支持的方法。   **7.TRACE:追踪路径**    TRACE方法是让Web服务器将之前的请求通信环回给客户端的方法。这个方法并不常用。   **8.CONNECT:要求用隧道协议连接代理**    CONNECT方法要求在与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信。主要使用SSL/TLS协议对通信内容加密后传输。   汇总:

在这里插入图片描述

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值