在C语言中不依赖libcurl调用REST API(适配Cortex M0低资源场景)
在Cortex-M0上无外部库实现REST API调用的可行方案
没问题,针对Cortex-M0这种资源极其紧凑的平台,完全可以手动实现轻量级的REST调用——毕竟REST本质就是基于HTTP的,而HTTP又构建在TCP之上,我们可以一步步从底层实现必要的核心功能,完全避开libcurl这类重型库的开销。下面是具体的落地方案:
核心思路
REST API调用本质就是发送符合HTTP规范的请求,并解析服务器返回的HTTP响应。我们只需要实现TCP连接建立、HTTP请求构造、HTTP响应解析这三个核心环节,全程不需要任何外部库。
步骤1:实现TCP连接(两种主流方式)
Cortex-M0一般没有内置TCP/IP栈,所以有两种低成本的实现方式:
方式A:借助WiFi模块的AT指令(推荐,代码量极小)
大部分低功耗WiFi模块(比如ESP8266、ESP32-C3)都支持AT指令集,通过UART串口就能控制模块建立TCP连接,完全不用自己写TCP栈:
- 先发送AT指令让模块连接WiFi:
AT+CWJAP="你的SSID","你的密码" - 再建立TCP连接到目标服务器:
AT+CIPSTART="TCP","api.example.com",80(或443如果用HTTPS,不过HTTPS需要额外处理证书,资源不够的话优先用HTTP)
方式B:手动实现极简TCP栈(适合有硬件以太网的场景)
如果你的开发板有以太网外设,可以自己实现一个极简版TCP栈:
- 只实现TCP三次握手、数据收发、四次断开的核心逻辑
- 省略滑动窗口、拥塞控制等复杂特性(因为REST请求一般都是短连接,数据量小)
- 直接操作MAC/PHY外设的寄存器来收发以太网帧
步骤2:手动构造HTTP请求(REST的核心)
REST请求就是格式化的HTTP报文,我们可以用静态字符串直接构造,完全避免动态内存分配:
GET请求示例
// 静态构造GET请求,严格遵循HTTP格式 const char REST_GET_REQUEST[] = "GET /api/sensor/data HTTP/1.1\r\n" "Host: api.yourserver.com\r\n" "Connection: close\r\n" // 用短连接简化处理 "\r\n"; // 头部与Body的分隔空行
POST请求示例(带JSON Body)
// POST请求需要指定Content-Type和Content-Length const char REST_POST_REQUEST[] = "POST /api/sensor/upload HTTP/1.1\r\n" "Host: api.yourserver.com\r\n" "Content-Type: application/json\r\n" "Content-Length: 22\r\n" // 必须与Body字节数一致 "Connection: close\r\n" "\r\n" "{\"temp\":25.5,\"hum\":60}";
关键注意事项
- 必须严格使用
\r\n作为换行符,HTTP规范要求,服务器会拒绝格式错误的请求 - 用
Connection: close告诉服务器请求完成后断开连接,避免处理持久连接的额外开销 - 尽量用静态字符数组存储请求,不要用
malloc分配内存(Cortex-M0的RAM一般只有几KB,动态分配容易碎片化)
步骤3:极简HTTP响应解析
我们不需要解析整个HTTP响应,只需要提取核心信息:
- 状态码:从响应第一行(比如
HTTP/1.1 200 OK)判断请求是否成功 - 响应Body:跳过所有HTTP头部,直接读取
\r\n\r\n之后的内容
解析示例代码(简化版)
#define BUF_SIZE 256 // 根据RAM大小调整,256字节足够处理大部分场景 void parse_http_response(char *buf, int len) { buf[len] = '\0'; // 确保字符串结束 // 检查状态码是否为200(成功) if (strstr(buf, "HTTP/1.1 200 OK") == NULL) { // 处理请求失败的情况 return; } // 找到Body的起始位置(跳过头部) char *body_start = strstr(buf, "\r\n\r\n"); if (body_start != NULL) { body_start += 4; // 跳过"\r\n\r\n" // 这里处理Body内容,比如提取JSON字段 // 比如找"temp"字段: char *temp_str = strstr(body_start, "\"temp\":"); if (temp_str != NULL) { float temp = atof(temp_str + 7); // 从": "后开始解析数字 // 处理温度值... } } }
优化技巧
- 如果你的C标准库没有
strstr,自己写一个极简版的字符串查找函数,只找固定子串 - 不要把整个响应存到内存里,边接收边解析,比如每收到一段数据就检查是否包含
\r\n\r\n,找到后直接处理后续内容
资源优化的关键细节
针对Cortex-M0的资源限制,一定要注意这些细节:
- 用静态缓冲区代替动态内存分配,比如
char buf[256];,避免内存碎片化 - 关闭不必要的外设(比如ADC、SPI),释放更多RAM给网络操作
- 尽量用HTTP而不是HTTPS(HTTPS需要证书存储和加密运算,对M0来说开销太大)
- 如果必须用HTTPS,可以考虑用支持AT指令的WiFi模块直接处理HTTPS(比如ESP8266的
AT+CIPSTART="SSL",...),不用自己实现加密
完整示例框架(基于WiFi AT指令)
#include <stdint.h> #include <string.h> #include "uart.h" // 你需要自己实现UART收发函数 #include "tick.h" // 你需要自己实现系统滴答计数函数 #define BUF_SIZE 256 // 发送AT指令并等待预期响应 int send_at_cmd(const char *cmd, const char *expected_resp, uint32_t timeout_ms) { uart_send_str(cmd); char buf[BUF_SIZE]; uint32_t start_tick = get_system_tick(); while ((get_system_tick() - start_tick) < timeout_ms) { int recv_len = uart_recv_buf(buf, BUF_SIZE); if (recv_len > 0) { buf[recv_len] = '\0'; if (strstr(buf, expected_resp) != NULL) { return 0; // 指令执行成功 } } } return -1; // 超时失败 } // 发起REST GET请求 int rest_get(const char *host, const char *path) { // 1. 建立TCP连接 char cmd[BUF_SIZE]; snprintf(cmd, BUF_SIZE, "AT+CIPSTART=\"TCP\",\"%s\",80\r\n", host); if (send_at_cmd(cmd, "CONNECT", 5000) != 0) { return -1; } // 2. 构造并发送GET请求 char get_req[BUF_SIZE]; snprintf(get_req, BUF_SIZE, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "\r\n", path, host); // 先发送CIPSEND指令准备发送数据 snprintf(cmd, BUF_SIZE, "AT+CIPSEND=%d\r\n", strlen(get_req)); if (send_at_cmd(cmd, ">", 1000) != 0) { send_at_cmd("AT+CIPCLOSE\r\n", "CLOSED", 1000); return -1; } uart_send_str(get_req); // 3. 接收并解析响应 char resp_buf[BUF_SIZE]; uint32_t start_tick = get_system_tick(); int is_success = 0; while ((get_system_tick() - start_tick) < 10000) { int recv_len = uart_recv_buf(resp_buf, BUF_SIZE); if (recv_len > 0) { parse_http_response(resp_buf, recv_len); if (strstr(resp_buf, "HTTP/1.1 200 OK") != NULL) { is_success = 1; } // 检查是否收到连接关闭的响应 if (strstr(resp_buf, "CLOSED") != NULL) { break; } } } // 4. 确保连接关闭 send_at_cmd("AT+CIPCLOSE\r\n", "CLOSED", 1000); return is_success ? 0 : -1; } int main(void) { // 初始化硬件 uart_init(115200); // 连接WiFi模块的UART system_tick_init(); // 连接WiFi if (send_at_cmd("AT+CWJAP=\"YOUR_SSID\",\"YOUR_PASSWORD\"\r\n", "WIFI CONNECTED", 10000) != 0) { while(1); // WiFi连接失败,死循环等待处理 } // 发起REST GET请求 rest_get("api.yourserver.com", "/api/sensor/data"); while(1); }
上面的代码需要你根据自己的硬件实现uart_send_str、uart_recv_buf、get_system_tick这些基础函数,逻辑非常简单,就是操作UART寄存器和系统滴答定时器。
内容的提问来源于stack exchange,提问作者Bhanz
相关产品推荐
相关产品推荐

