【IoT死磕系列】Day 3:学习HTTP!实战:STM32手写GET请求获取天气实战(附源码+八股文)

目录

[一、 核心概念: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,你必须附带上面这一大坨英文字母!

  1. 纯文本传输: 所有的控制信息全是用 ASCII 码组成的字符串。

  2. 极其浪费流量: 为了发2个字节的有效数据,你可能要附带 100多个字节 的废话(请求行、Host、浏览器版本等)。如果你的设备用的是按流量计费的NB-IoT卡,月底老板看了账单会打死你。

  3. 解析极其痛苦: 注意上面每一行结尾的 \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


五、 本文专业名词字典

  1. HTTP (HyperText Transfer Protocol): 超文本传输协议。规定了客户端和服务器怎么互相用文字打招呼。

  2. URI/URL: 统一资源定位符。通俗说就是"网址"或者API接口地址。

  3. JSON (JavaScript Object Notation): 目前最流行的轻量级数据交换格式。长这样:{"temp": "25", "city": "Beijing"}。它其实也就是一段格式工整的字符串,需要用专门的C语言库(如 cJSON)去提取里面的键值对。

  4. Header / Body: HTTP报文的组成部分。Header是协议控制废话(收件人信息),Body才是你真正想要的有效载荷(箱子里的物品)。


六、 总结与明日预告

今天我们深挖了HTTP协议。

  • 它很简单,就是你一言我一语的文本对话。

  • 但是很臃肿,为了发几字节数据要陪绑上百字节的Header,对单片机极不友好。

  • 我们手写了底层的C代码,并解决了HTTP头部分离和内存越界的坑。

思考一个问题:

HTTP这种"我问一句,你答一句"的模式,在物联网里有个致命缺陷------服务器没办法主动呼叫设备! 假设你的手机APP想远程控制家里的空调打开,如果用HTTP,空调的单片机只能每秒钟去问服务器一次:"有我的命令吗?有我的命令吗?"------这叫轮询,极其浪费性能。

怎么解决?

明天(Day 4),我们将正式迎来物联网时代的绝对统治者、完美解决上述一切痛点的真神------MQTT协议!

相关推荐
喜欢吃燃面2 小时前
基础算法:枚举(上)
c++·学习·算法
石去皿2 小时前
小样本提示学习全指南:从 Zero-shot 到 Few-shot-LtM 的核心策略解析
学习
郝学胜-神的一滴2 小时前
计算思维:数字时代的超级能力
开发语言·数据结构·c++·人工智能·python·算法
m0_531237172 小时前
C语言-数组练习
c语言·开发语言·算法
今天你TLE了吗2 小时前
JVM学习笔记:第四章——虚拟机栈
java·jvm·笔记·后端·学习
识君啊2 小时前
Java 动态规划 - 力扣 零钱兑换与完全平方数 深度解析
java·算法·leetcode·动态规划·状态转移
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-滑动窗口》--09长度最小的子数串,10无重复字符的最长字串
c++·算法
速易达网络2 小时前
物联网仿真教学平台
物联网
知识分享小能手2 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019 数据库的备份与恢复 — 语法知识点及使用方法详解(19)
数据库·学习·sqlserver