【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 小时前
Agent 学习笔记 05:系统提示词应该怎么写
笔记·学习·prompt·agent·个人开发·few-shot·ai应用开发
LCG米2 小时前
STM32硬件CRC外设实测:多项式固定陷阱、CRC-32校验不匹配根因与软件查表法耗时对比落地
stm32·单片机·嵌入式硬件
取名真是2 小时前
Python 算法知识点从基础到进阶
开发语言·python·算法
罗西的思考2 小时前
【轻量化复刻大模型 / KIMI K3】KDA 模块交互式演示
人工智能·算法·机器学习
Dev_XH3 小时前
【成品设计】物联网防酒驾控制系统
物联网
正在走向自律3 小时前
【3/5】人工智能训练师 三级/高级工 考证学习方向:从“独立执行”到“设计、归因与全链条负责”
人工智能·学习·人工智能训练师·注方案设计·数据质控负责人·模型评测与调优辅助·ai训练项目交付
码间留白4 小时前
uni-app 跑上鸿蒙,发现蓝牙全瞎:我把 BLE 补齐的实战(一)基建篇
物联网·uniapp·harmonyos next·蓝牙 ble·鸿蒙os开发
Yves..4 小时前
HTTP 协议学习笔记:从报文结构到抓包实践
网络协议·http·fiddler
沫璃染墨4 小时前
从零入门计算机网络系列(十二):HTTP长连接与会话管理详解——从Connection到Cookie与Session
linux·服务器·c++·tcp/ip·ubuntu·http·交互
nm_zfyjt4 小时前
信息流广告投放优化实践:线索成本评估模型与转化链路重构(家装建材案例)
算法·重构·内蒙古朋友圈广告服务商·内蒙古朋友圈获客