目录
[一、 核心概念:HTTP到底是个啥?属于哪一层?](#一、 核心概念:HTTP到底是个啥?属于哪一层?)
[二、 常见疑惑:为什么物联网设备嫌弃HTTP"太臃肿"?](#二、 常见疑惑:为什么物联网设备嫌弃HTTP“太臃肿”?)
[三、 企业级实战:STM32 + RTOS 纯C语言抓取天气API](#三、 企业级实战:STM32 + RTOS 纯C语言抓取天气API)
[四、 经典八股文与踩坑指南(面试必问)](#四、 经典八股文与踩坑指南(面试必问))
[五、 本文专业名词字典](#五、 本文专业名词字典)
[六、 总结与明日预告](#六、 总结与明日预告)
昨天我们搞懂了TCP/IP,知道它是网络世界的"保价顺丰快递",负责把数据一个字节不差地送达。但是,TCP只负责"送",不负责"解释"。如果你收到一个快递,里面只有一堆0和1,你根本不知道这是温度数据、还是一张图片、还是一段乱码。
所以,我们需要在TCP之上,再制定一套**"人类和服务器都能看懂的文本规范"**。
这就是今天的主角:统治了互联网半壁江山,却让单片机苦不堪言的 HTTP(超文本传输协议)。
一、 核心概念:HTTP到底是个啥?属于哪一层?
-
所属层级: 站在千层饼的最顶端------应用层。它的底层通常踩着TCP。
-
核心机制: 请求 - 响应模型 (Request-Response)。
-
通俗理解: 就像你去餐馆点菜。你(客户端)喊一嗓子"老板,来份宫保鸡丁"(发起请求 Request),老板(服务器)端出一盘菜并说"您的菜齐了"(返回响应 Response)。你如果不喊,老板永远不会主动给你端菜。这叫"无状态"且"被动"。
在物联网里,如果我们想让设备主动去服务器拉取当前的时间、天气,或者下载一个OTA固件包,HTTP依然是最稳妥的选择。
二、 常见疑惑:为什么物联网设备嫌弃HTTP"太臃肿"?
很多初学者觉得HTTP很简单,不就是调个API吗?但是在嵌入式C语言里,你是要亲自拼接底层字符串的!
当我们在浏览器输入一个网址,或者让单片机去请求天气时,底层到底发了什么?
我们用抓包工具来看一个真实的 HTTP GET 请求头部 (Header):
GET /v3/weather/now?location=beijing&key=your_api_key HTTP/1.1\r\n
Host: api.seniverse.com\r\n
User-Agent: STM32_RTOS_Client/1.0\r\n
Accept: */*\r\n
Connection: close\r\n
\r\n
发现致命问题了吗?
假设你的单片机只是想告诉服务器:"我的温度是25度"。如果用HTTP,你必须附带上面这一大坨英文字母!
-
纯文本传输: 所有的控制信息全是用 ASCII 码组成的字符串。
-
极其浪费流量: 为了发2个字节的有效数据,你可能要附带 100多个字节 的废话(请求行、Host、浏览器版本等)。如果你的设备用的是按流量计费的NB-IoT卡,月底老板看了账单会打死你。
-
解析极其痛苦: 注意上面每一行结尾的
\r\n(回车换行)。在C语言里,你得写循环去寻找连续的\r\n\r\n,才能区分哪里是废话(头部),哪里是真数据(Body)。
这就是为什么我们在资源极其受限的物联网设备中,更偏爱轻量级MQTT的原因(MQTT的固定头部只有2个字节!)。
三、 企业级实战:STM32 + RTOS 纯C语言抓取天气API
尽管HTTP很臃肿,但为了对接各种现成的互联网服务(比如天气、时间同步),我们依然得硬着头皮写。
假设我们要给桌面天气时钟项目拉取实时天气。我们对接心知天气(Seniverse)的免费API。昨天我们写了TCP Socket的框架,今天我们把HTTP的灵魂注入进去。
#include "lwip/sockets.h"
#include "string.h"
#include "stdio.h"
#define WEATHER_SERVER_IP "116.62.81.138" // api.seniverse.com 的IP
#define WEATHER_SERVER_PORT 80
// 这个是你要发送的极其臃肿的HTTP GET请求文本 (注意 \r\n 是严格规范,少一个都不行)
const char *http_get_request =
"GET /v3/weather/now.json?key=你的免费API密钥&location=beijing&language=zh-Hans&unit=c HTTP/1.1\r\n"
"Host: api.seniverse.com\r\n"
"User-Agent: STM32_Weather_Clock\r\n"
"Connection: close\r\n" // 告诉服务器,发完数据就断开TCP连接,单片机要省内存
"\r\n"; // 必须以一个空行 (连续的\r\n\r\n) 结束头部!!!
void fetch_weather_task(void *pvParameters) {
int sock = -1;
struct sockaddr_in dest_addr;
// 坑1:接收Buffer一定要够大!HTTP响应头动辄几百字节,如果太小会引发内存越界HardFault
char rx_buffer[1024];
while (1) {
// 1. 建立TCP连接 (沿用昨天的知识)
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(WEATHER_SERVER_PORT);
dest_addr.sin_addr.s_addr = inet_addr(WEATHER_SERVER_IP);
connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
// 2. 发送拼装好的HTTP报文
printf("正在发送HTTP请求...\n");
send(sock, http_get_request, strlen(http_get_request), 0);
// 3. 接收服务器的响应数据
int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
if (len > 0) {
rx_buffer[len] = '\0'; // 强行截断,变成安全字符串
// 坑2:极其关键的解析步骤!
// 服务器返回的数据是:[HTTP响应头] + \r\n\r\n + [真正的JSON天气数据]
// 我们不能把整个报文扔给JSON解析器,它会报错的!必须跳过头部!
char *json_body = strstr(rx_buffer, "\r\n\r\n"); // 寻找空行分界线
if (json_body != NULL) {
json_body += 4; // 指针往后挪4个字节,跳过 \r\n\r\n
printf("成功剥离HTTP头部,真正的天气JSON数据是:\n%s\n", json_body);
// 接下来就可以用 cJSON 库去解析 json_body 提取温度了
// parse_weather_json(json_body);
} else {
printf("未找到HTTP数据体,报文可能被截断了!\n");
}
}
close(sock); // TCP四次挥手断开
vTaskDelay(10 * 60 * 1000 / portTICK_PERIOD_MS); // 延时10分钟更新一次天气
}
}
四、 经典八股文与踩坑指南(面试必问)
八股 1:GET 和 POST 有什么本质区别?
-
通俗理解: GET 是"查询",POST 是"提交"。
-
格式区别:
-
GET 把参数明文挂在 URL 后面(比如
?location=beijing),没有请求体(Body)。受限于URL长度,传的数据极少。 -
POST 把数据放在请求体(Body)里,可以传极大的数据(比如上传摄像头拍的一张照片)。
-
-
安全区别: 面试官常问"谁更安全"。**标准答案:都不安全!**如果不加 HTTPS(SSL/TLS加密),GET和POST在Wireshark抓包下全是一丝不挂的明文。
八股 2:什么是 HTTP 状态码?说几个常见的。
-
就像老板给你端菜的反馈:
-
200 OK: 请求成功。
-
400 Bad Request: 客户端出问题了。你写的GET请求格式错了,服务器看不懂。
-
404 Not Found: 找不到资源。可能你请求的API地址拼写错了。
-
500 Internal Server Error: 服务器问题。后端代码写崩了,跟你单片机没关系。
-
嵌入式踩坑指南:Buffer溢出与分包问题
在PC上写代码,recv() 一次就能把几兆的数据收完。但在STM32上,由于TCP滑动窗口和LwIP内存池的限制,如果天气预报数据很长(比如包含了未来7天的天气),你调用一次 recv() 可能只收到了 HTTP头部和一半的JSON数据!
解决方案: 必须把 recv() 写在 while 循环里,把多次收到的碎片数据拼接到一个大的全局数组中,直到底层返回 0(表示服务器发完且关闭了连接)再去解析 \r\n\r\n。
五、 本文专业名词字典
-
HTTP (HyperText Transfer Protocol): 超文本传输协议。规定了客户端和服务器怎么互相用文字打招呼。
-
URI/URL: 统一资源定位符。通俗说就是"网址"或者API接口地址。
-
JSON (JavaScript Object Notation): 目前最流行的轻量级数据交换格式。长这样:
{"temp": "25", "city": "Beijing"}。它其实也就是一段格式工整的字符串,需要用专门的C语言库(如 cJSON)去提取里面的键值对。 -
Header / Body: HTTP报文的组成部分。Header是协议控制废话(收件人信息),Body才是你真正想要的有效载荷(箱子里的物品)。
六、 总结与明日预告
今天我们深挖了HTTP协议。
-
它很简单,就是你一言我一语的文本对话。
-
但是很臃肿,为了发几字节数据要陪绑上百字节的Header,对单片机极不友好。
-
我们手写了底层的C代码,并解决了HTTP头部分离和内存越界的坑。
思考一个问题:
HTTP这种"我问一句,你答一句"的模式,在物联网里有个致命缺陷------服务器没办法主动呼叫设备! 假设你的手机APP想远程控制家里的空调打开,如果用HTTP,空调的单片机只能每秒钟去问服务器一次:"有我的命令吗?有我的命令吗?"------这叫轮询,极其浪费性能。
怎么解决?
明天(Day 4),我们将正式迎来物联网时代的绝对统治者、完美解决上述一切痛点的真神------MQTT协议!