-
关注嘉友创科技公众号
- 源码地址:https://github.com/HX-IoT/ESP32-Developer-Guide
- ESP32开发指南QQ群:824870185,内有pdf版,排版整洁。
学习目的及目标
- 掌握HTTP原理和工作过程
- 掌握乐鑫ESP32HTTP获取服务器温度的程序设计
HTTP原理
HTTP是一套计算机网络通讯规则。下面只讲下请求格式,其他原理。
HTTP请求格式
HTTP请求是客户端往服务端发送请求动作,告知服务器自己的要求。其中信息由三部分组成:
- 请求方法,URI协议/版本:包括请求方式Method、资源路径URL、协议版本Version
- 请求头:包括一些访问的域名、用户代理、Cookie等信息
- 请求正文:就是HTTP请求的数据
备注:请求方式Method一般有GET、POST、PUT、DELETE,含义分别是获取、修改、上传、删除,其中GET方式仅仅为获取服务器资源,方式较为简单,因此在请求方式为GET的HTTP请求数据中,请求正文部分可以省略,直接将想要获取的资源添加到URL中。下图所示就是GET的请求,没有请求正文。详细的说明在下边。
现在大多数协议版本为http/1.1
1 2 3 4 5 6 7 8 9 | GET/sample.jspHTTP/1.1 Accept:image/gif.image/jpeg,*/* Accept-Language:zh-cn Connection:Keep-Alive Host:localhost User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0) Accept-Encoding:gzip,deflate
username=jinqiao&password=1234 |
服务器收到了客户端发来的HTTP请求后,根据HTTP请求中的动作要求,服务端做出具体的动作,将结果回应给客户端,称为HTTP响应。数据主要由三部分组成:HTTP 应答格式
- 协议状态:包括协议版本Version、状态码Status Code、回应短语
- 响应头:包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息
- 响应正文:就是响应的具体数据
1 2 3 4 5 6 | HTTP/1.1 200 OK Server:Apache Tomcat/5.0.12 Date:Mon,6Oct2003 13:23:42 GMT Content-Length:112
data(返回数据) |
备注:我们主要关心并且能够在客户端浏览器看得到的是三位数的状态码,不同的状态
码代表不同的含义,其中
1xx | 表示HTTP请求已经接受,继续处理请求 |
2xx | 表示HTTP请求已经处理完成 |
3xx | 表示把请求访问的URL重定向到其他目录 |
4xx | 表示客户端出现错误 |
5xx | 表示服务端出现错误 |
常见状态码的含义:
200---OK/请求已经正常处理完毕
301---/请求永久重定向
302---/请求临时重定向
304---/请求被重定向到客户端本地缓存
400---/客户端请求存在语法错误
401---/客户端请求没有经过授权
403---/客户端的请求被服务器拒绝,一般为客户端没有访问权限
404---/客户端请求的URL在服务端不存在
500---/服务端永久错误
503---/服务端发生临时错误
HTTP报文格式(原文)
HTTP报文是HTTP应用程序之间传输的数据块,HTTP报文分为HTTP请求报文和HTTP响应报文,但是无论哪种报文,他的整体格式是类似的,大致都是由起始、首部、主体三部分组成,起始说明报文的动作,首部说明报文的属性,主体则是报文的数据。接下来具体说明。
HTTP请求报文
请求报文的起始由请求行构成(有些资料称为状态行,名字不一样而已,都是指的一个东西),用来说明该请求想要做什么,由<Method>、<URL>、<Version> 三个字段组成,注意每个字段之间都有一个空格。
其中<Method>字段有不同的值:
GET --- 访问服务器的资源
POST --- 向服务器发送要修改的数据
HEAD --- 获取服务器文档的首部
PUT --- 向服务器上传资源
DELETE--- 删除服务器的资源
<URL>字段表示服务器的资源目录定位
<Version>字段表示使用的http协议版本
首部部分由多个请求头(也叫首部行)构成,那些首部字段名有如下,不全:
Accept 指定客户端能够接收的内容格式类型
Accept-Language 指定客户端能够接受的语言类型
Accept-Ecoding 指定客户端能够接受的编码类型
User-Agent 用户代理,向服务器说明自己的操作系统、浏览器等信息
Connection 是否开启持久连接(keepalive)
Host 服务器域名
...
主体部分就是报文的具体数据。
HTTP响应报文
响应报文的起始由状态行构成,用来说明服务器做了什么,由<Version>、<Status-Code>、<Phrase>三个字段组成,同样的每个字段之间留有空格;
<Status-Code> 上边已经说明;
首部由多个响应头(也叫首部行)组成, 首部字段名如下,不全:
Server 服务器软件名,Apache/Nginx
Date 服务器发出响应报文的时间
Last-Modified 请求资源的最后的修改时间
...
主体部分是响应报文的具体数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | { "results": [{ "location": { "id": "WX4FBXXFKE4F", "name": "北京", "country": "CN", "path": "北京,北京,中国", "timezone": "Asia/Shanghai", "timezone_offset": "+08:00" }, "daily": [{ //返回指定days天数的结果 "date": "2015-09-20", //日期 "text_day": "多云", //白天天气现象文字 "code_day": "4", //白天天气现象代码 "text_night": "晴", //晚间天气现象文字 "code_night": "0", //晚间天气现象代码 "high": "26", //当天最高温度 "low": "17", //当天最低温度 "precip": "0", //降水概率,范围0~100,单位百分比 "wind_direction": "", //风向文字 "wind_direction_degree": "255", //风向角度,范围0~360 "wind_speed": "9.66", //风速,单位km/h(当unit=c时)、mph(当unit=f时) "wind_scale": "" //风力等级 }, { "date": "2015-09-21", "text_day": "晴", "code_day": "0", "text_night": "晴", "code_night": "0", "high": "27", "low": "17", "precip": "0", "wind_direction": "", "wind_direction_degree": "157", "wind_speed": "17.7", "wind_scale": "3" }, { ... //更多返回结果 }], "last_update": "2015-09-20T18:00:00+08:00" //数据更新时间(该城市的本地时间) }] } |
JSON解析
此处HTTP获取城市的温度是访问心知天气的服务器,心知天气返回的数据是json格式,那么我们就需要使用到第三方的开源库cJSON了。ESP32的SDK已经自带这些移植好的库了,我们只需要直接使用即可。天气预报的数据格式如下所示:
关于cjson的使用,可参考。
软件设计
HTTP获取城市温度的主逻辑
ESP32的HTTP详细过程逻辑
ESP32的HTTP接口介绍
同TCP接口,因为此处是使用TCP数据包模拟HTTP包,完成发送和读取。
更多更详细接口请参考官方指南。
ESP32的HTTP总结
初始化wifi配置后,程序会根据WIFI的实时状态,在回调函数中给出状态返回,所以只需要在回调中进行相关操作,STA开始事件触发SC开始进行一键配置,在SC的回调中处理SC配置过程的事件,sc完成后,WIFI连上网后,就开始了HTTP的工作过程,很简单。
HTTP任务编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | void http_get_task(void *pvParameters) { 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[1024]; char mid_buf[1024]; int index; while(1) {
//DNS域名解析 int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res); if(err != 0 || res == NULL) { ESP_LOGE(HTTP_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(HTTP_TAG, "DNS lookup succeeded. IP=%s\r\n", inet_ntoa(*addr));
//新建socket s = socket(res->ai_family, res->ai_socktype, 0); if(s < 0) { ESP_LOGE(HTTP_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(HTTP_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(HTTP_TAG, "... socket send failed\r\n"); close(s); vTaskDelay(4000 / portTICK_PERIOD_MS); continue; } //清缓存 memset(mid_buf,0,sizeof(mid_buf)); //获取http应答包 do { bzero(recv_buf, sizeof(recv_buf)); r = read(s, recv_buf, sizeof(recv_buf)-1); strcat(mid_buf,recv_buf); } while(r > 0); //json解析 cjson_to_struct_info(mid_buf); //关闭socket,http是短连接 close(s);
//延时一会 for(int countdown = 10; countdown >= 0; countdown--) { vTaskDelay(1000 / portTICK_PERIOD_MS); } } } |
温度数据JSON解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void cjson_to_struct_info(char *text) { cJSON *root,*psub; cJSON *arrayItem; //截取有效json char *index=strchr(text,'{'); strcpy(text,index);
root = cJSON_Parse(text);
if(root!=NULL) { psub = cJSON_GetObjectItem(root, "results"); arrayItem = cJSON_GetArrayItem(psub,0);
cJSON *locat = cJSON_GetObjectItem(arrayItem, "location"); cJSON *now = cJSON_GetObjectItem(arrayItem, "now"); if((locat!=NULL)&&(now!=NULL)) { psub=cJSON_GetObjectItem(locat,"name"); sprintf(weathe.cit,"%s",psub->valuestring); ESP_LOGI(HTTP_TAG,"city:%s",weathe.cit);
psub=cJSON_GetObjectItem(now,"text"); sprintf(weathe.weather_text,"%s",psub->valuestring); ESP_LOGI(HTTP_TAG,"weather:%s",weathe.weather_text);
psub=cJSON_GetObjectItem(now,"code"); sprintf(weathe.weather_code,"%s",psub->valuestring); //ESP_LOGI(HTTP_TAG,"%s",weathe.weather_code);
psub=cJSON_GetObjectItem(now,"temperature"); sprintf(weathe.temperatur,"%s",psub->valuestring); ESP_LOGI(HTTP_TAG,"temperatur:%s",weathe.temperatur);
//ESP_LOGI(HTTP_TAG,"--->city %s,weather %s,temperature %s<---\r\n",weathe.cit,weathe.weather_text,weathe.temperatur); } } //ESP_LOGI(HTTP_TAG,"%s 222",__func__); cJSON_Delete(root); } |
效果展示
测试流程
- SmartConfig快配账号密码
- 自动连服务器获取温度
- 串口打开即可看获取的温度
效果展示
SmartConfig配置
获取温度
HTTP总结
- 此处HTTP是使用TCP模拟的,所以过程和TCP章类似。
- HTTP部分参考官方的源码,sc参考官方源码。
- Sc没有保存密码。
- 源码地址:https://github.com/xiaolongba/wireless-tech