🔓 从零理解 HTTP · TCP · TLS:用 WB2 芯片访问心知天气
一块 Ai-WB2 开发板 · 一行 HTTPS GET 请求 · 拆开看 TCP 怎么握手、TLS 怎么加密、HTTP 怎么传数据
HTTP TCP TLS mbedTLS 心知天气 API Ai-WB2
📖 一、HTTP 是什么
当你打开浏览器输入 https://api.seniverse.com/v3/weather/now.json,浏览器干了什么?
其实就一句话:按约定格式写了一封信,通过 TCP 管道寄出去,然后等回信。
"GET /v3/weather/now.json?key=xxx&location=shenzhen HTTP/1.1\r\n"
"Host: api.seniverse.com\r\n"
"User-Agent: Ai-WB2 XEMOWO\r\n"
"Accept: application/json\r\n"
"Connection: close\r\n"
"\r\n"
这就是 HTTP 的全部——一套纯文本的格式约定。
HTTP 请求的结构
GET /v3/weather/now.json HTTP/1.1 ← 请求行:方法 + 路径 + 版本
Host: api.seniverse.com ← 请求头:告诉服务器你是谁
User-Agent: Ai-WB2 XEMOWO ←
Accept: application/json ←
Connection: close ←
← 空行!表示头结束,开始消息体
HTTP 响应的结构
HTTP/1.1 200 OK ← 状态行:协议 + 状态码
Server: nginx ← 响应头:服务器信息
Content-Type: application/json ←
← 空行!
{"results":[{...}]} ← 响应体:实际数据(JSON)
就这么简单。HTTP 不是魔法,它就是一段带格式的字符串。
🧱 二、HTTP 跑在哪?——TCP
HTTP 只是一段文本。文本怎么传到服务器?靠 TCP。
┌──────────────────────────────┐
│ HTTP: "GET /weather HTTP/1.1"│ ← 应用层:定义"发什么"
├──────────────────────────────┤
│ TCP: 分段、编号、确认、重传 │ ← 传输层:定义"怎么传"
├──────────────────────────────┤
│ IP: 寻址、路由 │ ← 网络层:定义"往哪传"
├──────────────────────────────┤
│ WiFi: 无线电信号 │ ← 物理层
└──────────────────────────────┘
TCP 做了三件事:
- 分段:把 HTTP 报文切成一帧一帧的小包,每帧最多 1460 字节
- 保证到达:每个包都有编号。丢了就重发,乱序就排序,重复就去重
- 流量控制:服务器太慢就慢点发,太快就快发
sequenceDiagram
participant A as 🔵 WB2 开发板
participant B as 📡 服务器
Note over A,B: TCP 三次握手
A->>B: SYN, seq=1000
B->>A: SYN+ACK, seq=5000, ack=1001
A->>B: ACK, ack=5001
Note over A,B: TCP 连接建立,开始传 HTTP
A->>B: PSH, seq=1001, data="GET /weather HTTP/1.1..."
B->>A: ACK, ack=1245
B->>A: PSH, seq=5001, data="HTTP/1.1 200 OK..."
A->>B: ACK, ack=5672
Note over A,B: TCP 四次挥手关闭
关键区别:TCP 是管道,HTTP 是管道里流的水。
TCP 不关心你传的是 HTTP、MQTT 还是视频流——它只负责把字节完整送到。
🔐 三、HTTPS 多了什么?——TLS
HTTP 是明文。任何人抓包都能看到你发了什么。
HTTPS = HTTP + TLS。TLS 在 TCP 和 HTTP 之间加了一层加密:
HTTP HTTPS
────── ──────────
应用层 │ GET /weather │ GET /weather
│ │
TLS 层 │ (无) │ 加密握手、证书验证、对称加密
│ │
传输层 │ TCP │ TCP
TLS 握手做了三件事:
- 证书验证:服务器出示证书,客户端验证"你是不是 api.seniverse.com"
- 密钥协商:双方协商出一个临时的对称密钥
- 加密通信:之后所有 HTTP 报文都用这个密钥加密
你的 WB2 日志里这行就是握手结果:
TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256
│ │ │ │ │
│ │ │ │ └─ 哈希算法:SHA-256
│ │ │ └─ 加密算法:AES-128-GCM
│ │ └─ 身份验证:RSA 签名
│ └─ 密钥交换:ECDHE(椭圆曲线,前向安全)
└─ 传输方式:TLS 1.2+
🔬 四、实战:一步一步拆解请求过程
用 WB2 的串口输出,你能看到每一层的真实耗时。
硬件
- 🔵 Ai-WB2 开发板 × 1 (BL602 RISC-V)
- 🔌 USB Type-C 数据线
- 📶 可上网的 Wi-Fi
代码流程
sequenceDiagram
participant A as 🔵 WB2
participant B as 📡 api.seniverse.com
Note over A: 开始计时 t0
A->>B: 🔗 DNS 解析 + TCP 三次握手
Note over A: t1 - t0 = Connect time
A->>B: 🔐 TLS 握手(证书+密钥协商)
Note over A: t3 - t2 = Handshake time
A->>B: 📤 HTTP GET 请求
Note over A: t3 → 开始等
B->>A: 📥 HTTP 200 + JSON 数据
Note over A: t4 - t3 = Server latency
Note over A: t4 - t0 = Total RTT
核心代码
#define WEATHER_API_KEY "SY6RZjFvE-3_ogTp_"
#define WEATHER_LOCATION "shenzhen"
#define WEB_SERVER "api.seniverse.com"
#define WEB_PORT "443"
#define WEB_URL "https://api.seniverse.com/v3/weather/now.json" \
"?key=" WEATHER_API_KEY \
"&location=" WEATHER_LOCATION \
"&language=en&unit=c"
/* HTTP 请求报文 —— 一段纯文本 */
static const char HTTP_REQUEST[] =
"GET " WEB_URL " HTTP/1.1\r\n"
"Host: " WEB_SERVER "\r\n"
"User-Agent: Ai-WB2 XEMOWO\r\n"
"Accept: application/json\r\n"
"Accept-Encoding: identity\r\n"
"Connection: close\r\n"
"\r\n";
static void https_weather_task(void *pvParameters)
{
long long t0, t1, t2, t3, t4;
/* 1. TCP 连接(含 DNS 解析) */
t0 = aos_now_ms();
mbedtls_net_connect(&server_fd, WEB_SERVER, WEB_PORT,
MBEDTLS_NET_PROTO_TCP);
t1 = aos_now_ms();
/* 2. TLS 握手 */
t2 = aos_now_ms();
while (mbedtls_ssl_handshake(&ssl) != 0) ;
t3 = aos_now_ms();
/* 3. 发送 HTTP 请求 */
mbedtls_ssl_write(&ssl, HTTP_REQUEST, strlen(HTTP_REQUEST));
/* 4. 接收 HTTP 响应 */
char response[4096];
int total = mbedtls_ssl_read(&ssl, response, sizeof(response));
t4 = aos_now_ms();
/* 解析 JSON,提取天气 */
cJSON *root = cJSON_Parse(extract_body(response));
char *city = cJSON_GetObjectItem(root, "results[0].location.name");
char *text = cJSON_GetObjectItem(root, "results[0].now.text");
char *temp = cJSON_GetObjectItem(root, "results[0].now.temperature");
/* 打印天气 + 各层耗时 */
printf("| API | City | %s |\n", city);
printf("| API | Weather | %s |\n", text);
printf("| API | Temperature | %s C |\n", temp);
printf("| TCP | Connect | %lld ms |\n", t1 - t0);
printf("| TLS | Handshake | %lld ms |\n", t3 - t2);
printf("| HTTP | Req/Res | %d/%d bytes |\n", req_len, total);
printf("| HTTP | Latency | %lld ms |\n", t4 - t3);
printf("| ALL | Total RTT | %lld ms |\n", t4 - t0);
}
📊 实际运行输出
graph LR
A[🔵 WB2] -->|"TCP Connect 234ms"| B[📡 服务器]
B -->|"TLS Handshake 1890ms"| C[🔐 加密通道]
C -->|"HTTP Request 244B"| D[📤 GET /weather]
D -->|"HTTP Response 671B"| E[📥 200 OK + JSON]
E -->|"cJSON 解析"| F[☁️ 天气展示]
+------+------------------+------------------+
| TYPE | ITEM | VALUE |
+------+------------------+------------------+
| API | City | Shenzhen |
| API | Weather | Cloudy |
| API | Temperature | 30 C |
| TCP | Connect time | 234 ms |
| TLS | Handshake time | 1890 ms |
| HTTP | Request size | 244 bytes |
| HTTP | Response size | 671 bytes |
| HTTP | Server latency | 312 ms |
| ALL | Total RTT | 2436 ms |
+------+------------------+------------------+
各字段说明
| 层级 |
表头 |
含义 |
代码对应 |
| API |
City |
城市名 |
JSON:location.name |
| API |
Weather |
天气现象 |
JSON:now.text |
| API |
Temperature |
当前气温 |
JSON:now.temperature |
| TCP |
Connect time |
DNS 解析 + TCP 三次握手耗时 |
mbedtls_net_connect() 前后计时 |
| TLS |
Handshake time |
证书验证 + 密钥协商耗时 |
mbedtls_ssl_handshake() 前后计时 |
| HTTP |
Request size |
GET 请求报文字节数 |
strlen(HTTP_REQUEST) |
| HTTP |
Response size |
服务器返回总字节数 |
mbedtls_ssl_read() 累计 |
| HTTP |
Server latency |
从发完请求到收完响应的时间 |
发→收 计时差 |
| ALL |
Total RTT |
端到端完整耗时 |
前三行之和 |
🆚 五、TCP vs TLS vs HTTP 一句话对比
| 层 |
是什么 |
干什么 |
在代码里 |
| TCP |
传输管道 |
把字节从 A 送到 B,保证不丢不乱 |
mbedtls_net_connect() |
| TLS |
加密保险箱 |
在 TCP 管道上套一层加密,防窃听 |
mbedtls_ssl_handshake() |
| HTTP |
数据格式 |
定义请求/响应的语法和语义 |
"GET /weather HTTP/1.1\r\n..." |
你写的文本 实际传输过程
───────── ────────────
"GET /weather" ──→ [TLS 加密] ──→ [TCP 分段] ──→ [WiFi 发送]
↓
{"text":"Cloudy"} ←── [TLS 解密] ←── [TCP 重组] ←── [WiFi 接收]
📝 六、编译 & 烧录
cd applications/xemowo/XEMOWO_TEST2
export BL60X_SDK_PATH=~/Ai-Thinker-WB2
make -j8 # 编译
make flash p=/dev/ttyUSB0 b=921600 # 烧录
✅ 七、你学到了什么
🚀 八、和 MQTT 对比
| 维度 |
HTTP (XEMOWO_TEST2) |
MQTT (XEMOWO_TEST1) |
| 模型 |
请求/响应 |
发布/订阅 |
| 谁主动 |
客户端拉 |
服务器推 |
| 连接 |
每次请求建一次 TCP+TLS |
长连接,复用 TCP |
| 每次耗时 |
~2400ms(TCP+TLS+传输) |
<50ms(已连接状态下) |
| 适合 |
定时查询、API 访问 |
实时消息、传感器上报 |
两个工程都在 applications/xemowo/ 下。一个拉,一个推。跑完两个,HTTP 和 MQTT 的区别用眼睛就能看到。
"HTTP is just text over TCP. HTTPS is the same text, encrypted."
你刚刚用 mbedTLS 和一块 RISC-V 芯片,亲手验证了这句话。
Ai-WB2 (BL602 RISC-V) · mbedTLS · 心知天气 API · 代码: applications/xemowo/XEMOWO_TEST2/