HTTP / HTTPS / MQTT / MQTTs(嵌入式学习版)

说明:裸机 / MCU 环境一般不会自己手写 TLS 底层,TLS 用 mbedtls/wolfssl 库 ;HTTP/MQTT 是应用层协议,C 写应用层解析。

推荐学习路线:

  1. 先看懂裸 TCP → HTTP(明文)
  2. 再引入 mbedtls 封装 TCP,实现 HTTPS
  3. 裸 TCP 实现 MQTT(明文)
  4. mbedtls 封装 TCP,实现 MQTTs(加密)
    环境建议:Linux PC 编译测试(方便,不用烧 MCU),代码可移植到 STM32/ESP32。

依赖:mbedtls库,安装:sudo apt install libmbedtls-dev

基础概念区分

表格

协议 底层 安全 端口
HTTP TCP 明文 无加密 80
HTTPS TCP + TLS TLS 传输加密 443
MQTT TCP 明文 无加密 1883
MQTTs TCP + TLS TLS 传输加密 8883

重点:HTTPS / MQTTs 不是协议本身加密,是在 TCP 和应用层之间套一层 TLS。应用层报文格式和明文版本完全一样,只是 TCP 传输的数据被 TLS 加密。


1. C 最简 HTTP Client(明文 TCP,Linux)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define BUF_SIZE 1024

int create_tcp_socket(const char *host, int port)
{
    struct hostent *he = gethostbyname(host);
    if (!he) return -1;

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) return -1;

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    memcpy(&server_addr.sin_addr.s_addr, he->h_addr, he->h_length);

    if (connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        close(fd);
        return -1;
    }
    return fd;
}

int main(void)
{
    int sockfd = create_tcp_socket("httpbin.org", 80);
    if (sockfd < 0)
    {
        perror("connect fail");
        return 1;
    }

    // HTTP GET 请求
    char req[] = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection:close\r\n\r\n";
    send(sockfd, req, strlen(req), 0);

    char buf[BUF_SIZE];
    ssize_t n;
    while ((n = recv(sockfd, buf, BUF_SIZE - 1, 0)) > 0)
    {
        buf[n] = 0;
        printf("%s", buf);
    }

    close(sockfd);
    return 0;
}

编译:gcc http.c -o http

原理:TCP 连接后直接发送 HTTP 文本请求,接收返回。全部明文。


2. HTTPS Client(TCP+mbedtls TLS 封装)

HTTPS = TCP + TLS 握手,握手成功后读写的是 TLS 加密流,上层 HTTP 报文和上面完全不变。

复制代码
#include <stdio.h>
#include <string.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/error.h>

#define BUF_SIZE 1024

int main(void)
{
    mbedtls_net_context net_ctx;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config ssl_cfg;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;

    mbedtls_net_init(&net_ctx);
    mbedtls_ssl_init(&ssl);
    mbedtls_ssl_config_init(&ssl_cfg);
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);

    // 随机数种子
    mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
    mbedtls_ssl_config_defaults(&ssl_cfg,
        MBEDTLS_SSL_IS_CLIENT,
        MBEDTLS_SSL_TRANSPORT_STREAM,
        MBEDTLS_SSL_PRESET_DEFAULT);

    mbedtls_ssl_conf_rng(&ssl_cfg, mbedtls_ctr_drbg_random, &ctr_drbg);
    // 生产环境必须开启证书校验!这里学习临时关闭
    mbedtls_ssl_conf_authmode(&ssl_cfg, MBEDTLS_SSL_VERIFY_NONE);

    mbedtls_ssl_setup(&ssl, &ssl_cfg);

    // 连接服务器 443端口
    int ret = mbedtls_net_connect(&net_ctx, "httpbin.org", "443", MBEDTLS_NET_PROTO_TCP);
    if(ret != 0) { printf("net connect fail\n"); goto cleanup; }

    mbedtls_ssl_set_bio(&ssl, &net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL);
    printf("TLS handshaking...\n");
    ret = mbedtls_ssl_handshake(&ssl);
    if(ret != 0)
    {
        char err_buf[1024];
        mbedtls_strerror(ret, err_buf, sizeof(err_buf));
        printf("handshake err: %s\n", err_buf);
        goto cleanup;
    }
    printf("TLS handshake ok\n");

    // HTTP GET,报文和明文HTTP完全一样!
    char req[] = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\nConnection:close\r\n\r\n";
    mbedtls_ssl_write(&ssl, (unsigned char*)req, strlen(req));

    unsigned char buf[BUF_SIZE];
    while(1)
    {
        ret = mbedtls_ssl_read(&ssl, buf, BUF_SIZE-1);
        if(ret <=0) break;
        buf[ret] = 0;
        printf("%s", buf);
    }

cleanup:
    mbedtls_ssl_close_notify(&ssl);
    mbedtls_net_free(&net_ctx);
    mbedtls_ssl_free(&ssl);
    mbedtls_ssl_config_free(&ssl_cfg);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    mbedtls_entropy_free(&entropy);
    return 0;
}

编译:gcc https.c -o https -lmbedtls -lmbedx509 -lmbedcrypto

关键点:

  • TLS 握手成功后,mbedtls_ssl_write/read替代原生send/recv
  • HTTP 请求报文和明文 HTTP 一模一样,只是底层传输被 TLS 加密
    生产环境:不能关闭证书校验,需要加载 CA 根证书,防止中间人攻击

3. MQTT 明文 C 客户端(极简,自己实现 MQTT 报文)

MQTT 是二进制协议,不是文本。核心报文:CONNECT、SUBSCRIBE、PUBLISH。

下面极简示例,只演示 CONNECT + PUBLISH(发布一条消息到 1883 端口公共 MQTT broker test.mosquitto.org)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int create_tcp_socket(const char *host, int port)
{
    struct hostent *he = gethostbyname(host);
    if (!he) return -1;
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) return -1;
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    memcpy(&addr.sin_addr.s_addr, he->h_addr, he->h_length);
    if(connect(fd,(struct sockaddr*)&addr,sizeof(addr))<0) { close(fd); return -1; }
    return fd;
}

// 构造MQTT CONNECT报文
void build_mqtt_connect(unsigned char *pkt, int *len)
{
    unsigned char client_id[] = "test_client_001";
    int cli_len = sizeof(client_id)-1;
    int var_len = 2 + cli_len;
    *len = 2 + var_len;
    pkt[0] = 0x10; // CONNECT
    pkt[1] = var_len;
    pkt[2] = 0; pkt[3] = 4;
    memcpy(pkt+4, "MQTT",4);
    pkt[8] = 0x02; // clean session
    pkt[9] = 0; pkt[10] = 60; // keepalive
    pkt[11] = (cli_len>>8)&0xff;
    pkt[12] = cli_len &0xff;
    memcpy(pkt+13, client_id, cli_len);
}

int main(void)
{
    int fd = create_tcp_socket("test.mosquitto.org",1883);
    if(fd<0) { perror("tcp connect");return 1; }

    unsigned char pkt[128];
    int pkt_len;
    build_mqtt_connect(pkt, &pkt_len);
    send(fd, pkt, pkt_len, 0);

    unsigned char resp[64];
    recv(fd, resp, sizeof(resp),0);
    printf("CONNECT ACK rc=%d\n", resp[3]);

    // PUBLISH 报文,发送消息
    unsigned char pub[] = {
        0x30, 0x11, // PUBLISH
        0x00,0x04, // topic len
        't','e','s','t', // topic "test"
        'h','e','l','l','o',' ','m','q','t','t'
    };
    send(fd, pub, sizeof(pub),0);

    sleep(1);
    close(fd);
    return 0;
}

学习重点:MQTT 是二进制头部 + 可变长度编码,比 HTTP 复杂。

实际项目不会手写完整 MQTT 协议栈,常用:paho-mqtt C 库。

paho-mqtt(推荐,工业标准 MQTT C 库)

paho-mqtt 本身支持:明文 MQTT;对接 mbedtls/wolfssl 后直接 MQTTs。

  • MQTT:paho 直接用原生 socket
  • MQTTs:paho 注册 mbedtls 的读写回调,底层走 TLS,上层 MQTT 报文完全不变

4. MQTTs(MQTT over TLS)

逻辑和 HTTPS 完全一致:

  1. TCP 连接 broker 8883 端口
  2. mbedtls TLS 握手(校验证书)
  3. TLS 握手成功,用mbedtls_ssl_write/read收发 MQTT 二进制报文

MQTT 的 CONNECT/PUBLISH/SUBSCRIBE 报文和明文 MQTT 完全一样,只是 TCP 链路被 TLS 加密。

两种学习路径建议

路径 A(由浅入深,推荐)

  1. 原生 socket TCP → HTTP(看懂文本请求响应)
  2. 原生 socket TCP → MQTT(看懂二进制报文)
  3. mbedtls TLS 封装 socket → HTTPS
  4. mbedtls TLS 封装 socket → MQTTs

路径 B(工程化,项目用)

  • HTTP/HTTPS:使用curl库(libcurl),不用自己解析 HTTP
  • MQTT/MQTTs:使用 paho-mqtt + mbedtls,不用手写 MQTT 协议解析

重要安全要点(和你前面 OTA 关联)

  1. HTTPS/MQTTs 的 TLS 主要解决传输加密 + 身份认证,防止中间人抓包篡改。
  2. TLS 只能保护链路;固件本身下载到 flash 后仍是明文,所以 OTA 还要额外做固件 AES 加密 + 数字签名(就是上一节我们聊的 OTA 加密)。
  3. 证书校验不要关闭,关闭 TLS 等于没有安全,中间人攻击可以劫持。
  4. MCU 资源紧张时,优先 TLS1.2,TLS1.3 握手更快,但库体积更大。

常见坑

  1. MQTT 可变长度剩余长度编码很容易写错,手写协议栈容易 bug,学习可以写,项目尽量 paho。
  2. TLS 握手超时、证书过期、域名不匹配。
  3. 网络缓冲区过小,分包读写 TLS 流,需要处理粘包。
  4. MCU 上内存不足,mbedtls 栈 / 堆配置不够,握手失败。

HTTP / HTTPS / MQTT / MQTTs 协议栈分层框图

分层逻辑:应用层 ↔ TLS 层(可选) ↔ TCP 传输层 ↔ IP 网络层 ↔ 链路层

核心一句话:HTTPS/MQTTs 只是在【应用层和 TCP 之间插入 TLS 安全层】,上层应用报文格式完全不变。

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      应用层 Application                      │
│  ┌──────────┐        ┌──────────┐       ┌────────┐ ┌────────┐│
│  │   HTTP   │        │  HTTPS   │       │ MQTT   │ │ MQTTs  ││
│  │(明文文本)│        │HTTP报文 │        │(二进制)│ │MQTT报文││
│  └──────────┘        └────┬─────┘       └───┬────┘ └───┬────┘│
└───────────────────────────┼──────────────────┼───────────────┘
                            ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                     TLS 安全层(可选)                        │
│  明文HTTP 无TLS        │ HTTPS启用TLS1.2/1.3                │
│  明文MQTT 无TLS        │ MQTTs启用TLS1.2/1.3                 │
│  功能:握手、证书校验、加密、完整性校验、防中间人攻击           │
└───────────────────────────┬──────────────────┬───────────────┘
                            ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                     传输层 Transport TCP                     │
│                可靠字节流、连接、重传、流控                   │
│端口:HTTP:80  HTTPS:443    MQTT:1883   MQTTs:8883            │
└───────────────────────────┬──────────────────┬───────────────┘
                            ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                        网络层 IP                              │
│                      IP寻址、路由                             │
└───────────────────────────┬──────────────────┬───────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                   链路层 + 物理层(以太网/WiFi)              │
└─────────────────────────────────────────────────────────────┘

简化对比表(配套框图,方便学习)

表格

协议 TLS 层 传输层 报文类型 端口 安全特点
HTTP ❌ 无 TCP 文本 80 全部明文,抓包可读
HTTPS ✅ 有 TCP 文本 443 HTTP 报文被 TLS 加密传输
MQTT ❌ 无 TCP 二进制 1883 二进制明文,抓包可解析 payload
MQTTs ✅ 有 TCP 二进制 8883 MQTT 报文被 TLS 加密传输

关键知识点(结合你前面 OTA 场景)

  1. TLS 只加密 TCP 链路上传输的数据
    数据到达设备,TLS 解密后,给到上层 HTTP/MQTT 的仍然是明文应用报文。
    → 所以 OTA 下载固件就算用 HTTPS/MQTTs,固件写到 Flash 还是明文,必须额外做固件镜像加密 + 数字签名。
  2. 上层协议逻辑不变:
    HTTPS 的 HTTP 请求内容,和明文 HTTP 一模一样;MQTTs 的 CONNECT/PUBLISH 包和明文 MQTT 完全相同,TLS 只做透明加解密。
  3. TLS 职责:
    • 服务器身份校验(CA 证书,防中间人劫持)
    • 链路数据加密
    • 报文完整性校验

设备端代码视角分层(对应 C 代码)

复制代码
应用代码
   ├─ http_client() / mqtt_client() 【构造HTTP/MQTT报文】
   └─ 底层读写接口
         ├─ 明文:send() / recv() 直接调用socket
         └─ TLS加密:mbedtls_ssl_write() / mbedtls_ssl_read()
                ↓ mbedtls内部实现TLS握手、加解密
                    ↓ 底层仍然调用socket send/recv
相关推荐
xier_ran18 小时前
【infra之路】AI 编译器学习笔记(十三):AI 编译器全景——TVM、XLA、IREE 与其他
人工智能·笔记·学习
连山齐名18 小时前
ai应用开发的学习笔记
服务器·笔记·学习
xier_ran19 小时前
【infra之路】AI 编译器学习笔记(十四):为什么要 tiling,以及矩阵计算单元的“固定“
人工智能·笔记·学习
燕落南枝19 小时前
HTML 学习:辅助标签 label 与字符实体
前端·学习·html
hackchen19 小时前
LangChain 学习第 3 课 - 输出解析器(Output Parser)
数据库·学习·langchain
wangguanghou120 小时前
【学习成长】深入解析721学习成长模型:70-20-10法则的实践指南与避坑手册
学习
其实防守也摸鱼20 小时前
Linux文本处理命令理论学习
linux·服务器·前端·数据库·sql·学习·安全
dadaobusi20 小时前
学习: Triton
学习