安装与基础配置
**Mongoose 简介:**Mongoose 是一个用于 C/C++ 的嵌入式网络库,提供事件驱动的非阻塞 API 来处理 TCP、UDP、HTTP、WebSocket、MQTT 等协议 (Mongoose :: Documentation)。它跨平台支持 Windows、Linux、MacOS 以及 STM32、ESP32 等嵌入式设备,并可在操作系统上运行或在裸机上使用内置的 TCP/IP 协议栈 (Mongoose :: Documentation) (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。自 2004 年以来,众多产品使用 Mongoose 实现联网功能,其可靠性经过了长期检验。
**获取与集成:**Mongoose 以 单个源文件库 形式提供,非常易于集成。官方推荐的集成步骤如下 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket):
- **下载源码:**从官方仓库获取
mongoose.c
和mongoose.h
文件,并将其复制到工程的源代码目录中 (Mongoose :: Documentation)。这两个文件包含了 Mongoose 库的全部实现。 - **包含头文件:**在你的 C 源文件(如
main.c
)中包含头文件mongoose.h
(Mongoose :: Documentation)。 - **编译链接:**将
mongoose.c
添加到工程一起编译。例如,使用 GCC 时,可以直接将它与应用代码一起编译:
Mongoose 无需其他依赖库,一般无需特殊链接选项。gcc main.c mongoose.c -o myapp
- **基础初始化:**在代码中,创建一个事件管理器(
struct mg_mgr
),调用mg_mgr_init()
初始化它 (Mongoose :: Documentation)。之后,可调用相应的监听或连接函数(例如mg_http_listen()
)启动网络服务 (Mongoose :: Documentation)。最后在主循环中反复调用mg_mgr_poll()
处理事件 (Mongoose :: Documentation)。下面给出一个最小化示例程序。
#include "mongoose.h"
// 事件回调函数,处理传入连接的各类事件
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // 收到 HTTP 请求 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=static%20void%20fn%28struct%20mg_connection%20,else))
struct mg_http_message *hm = (struct mg_http_message *) ev_data; // 解析后的HTTP请求 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=static%20void%20fn%28struct%20mg_connection%20,else))
if (mg_match(hm->uri, mg_str("/api/hello"), NULL)) { // 判断URI是否为 /api/hello ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=if%20%28mg_match%28hm,else))
// 返回一个 JSON 响应,内容 {"status": 1}
mg_http_reply(c, 200, "", "{%m:%d}\n", MG_ESC("status"), 1); // 回复 JSON ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=if%20%28mg_match%28hm,%2F%2F%20Serve%20static))
} else {
// 对于其他请求,作为静态文件请求处理,根目录为当前目录
struct mg_http_serve_opts opts = {.root_dir = "."}; // 文档根目录 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=%7D%20else%20,%2F%2F%20Serve%20static%20files))
mg_http_serve_dir(c, hm, &opts); // 托管目录下的静态文件 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=%7D%20else%20,%2F%2F%20Serve%20static%20files))
}
}
}
int main(void) {
struct mg_mgr mgr; // Mongoose 事件管理器 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=struct%20mg_mgr%20mgr%3B%20%20%2F%2F,%2F%2F%20Infinite%20event%20loop))
mg_mgr_init(&mgr); // 初始化事件管理器 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=struct%20mg_mgr%20mgr%3B%20%20%2F%2F,%2F%2F%20Infinite%20event%20loop))
mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, NULL); // 监听 HTTP 8000 端口 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=struct%20mg_mgr%20mgr%3B%20%20%2F%2F,%2F%2F%20Infinite%20event%20loop))
for (;;) {
mg_mgr_poll(&mgr, 1000); // 事件循环,每秒阻塞最多1000ms ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=mg_http_listen%28%26mgr%2C%20,return%200))
}
mg_mgr_free(&mgr); // 程序结束时清理,关闭所有连接
return 0;
}
上述示例展示了 Mongoose 的基本集成和用法:初始化管理器,启动一个 HTTP 服务,并在事件回调中处理请求。编译运行该程序后,用浏览器访问 http://localhost:8000
即可看到目录列表或 API 响应 (Mongoose :: Documentation)。
**嵌入式构建注意:**如果在嵌入式平台上构建,建议创建一个自定义配置头文件
mongoose_config.h
并根据需要定义宏来启用特定功能或驱动。在编译时定义-DMG_ARCH=<Arch>
指定体系架构,或在mongoose_config.h
内定义。如需启用 Mongoose 内置的 TLS 支持或嵌入式网络驱动,可定义相应宏(后文详述) (Mongoose :: Documentation)。详情可参考官方文档的“Build options”部分 (Mongoose :: Documentation)。
事件循环与连接管理
Mongoose 使用事件驱动机制,通过一个事件管理器(struct mg_mgr
)统一管理所有网络连接和事件调度 (Mongoose :: Documentation)。开发者需要提供事件回调函数来处理各种事件(如连接就绪、数据到达等),Mongoose 将在内部检测网络I/O并调用回调函数。
mg_mgr 与 mg_connection
struct mg_mgr
(事件管理器):保存当前所有活动连接列表。使用mg_mgr_init()
初始化后,即可用于创建监听或发起连接 (Mongoose :: Documentation)。一个进程通常只需一个全局的mg_mgr
实例(除非需要多个独立事件循环)。struct mg_connection
(连接结构):表示一个网络连接,可以是监听socket、传入的客户端连接,或由本地主动发起的外连。每个连接在内部由 Mongoose 链入mg_mgr
的链表进行管理 (Mongoose :: Documentation)。mg_connection
包含许多字段,包括连接的状态标志、缓冲区等。所有字段默认对应用代码可见,以便高级场景下获得最大灵活性 (Mongoose :: Documentation)。
**初始化事件管理器:**使用 mg_mgr_init(struct mg_mgr *mgr)
初始化事件管理器结构 (Mongoose :: Documentation)。该函数会设置内部连接列表为空、配置默认 DNS 服务器等。一般在程序开始时调用一次。对应的,在程序结束或不再需要网络功能时,应调用 mg_mgr_free(struct mg_mgr *mgr)
来关闭所有连接并释放资源 (Mongoose :: Documentation)。
struct mg_mgr mgr;
mg_mgr_init(&mgr); // 初始化事件管理器 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=struct%20mg_mgr%20mgr%3B%20mg_mgr_init))
// ... 创建连接或监听 ...
// 事件循环(见下)...
mg_mgr_free(&mgr); // 关闭所有连接并释放资源 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=Usage%20example%3A))
**事件循环:**Mongoose 不使用独立线程,而需要显式调用 mg_mgr_poll(struct mg_mgr *mgr, int ms)
来驱动事件循环 (Mongoose :: Documentation)。mg_mgr_poll()
会检查所有连接的网络I/O事件,行为包括:
- 接受新的传入连接(对于监听socket)并产生
MG_EV_ACCEPT
事件 (Mongoose :: Documentation)。 - 读取各连接的传入数据,将数据填充到连接的接收缓冲区,并向该连接发送
MG_EV_READ
事件 (Mongoose :: Documentation)。 - 将各连接待发送缓冲区中的数据写出网络,并在成功写出后产生
MG_EV_WRITE
事件 (Mongoose :: Documentation)。 - 关闭已标记关闭或发生错误的连接,并发送
MG_EV_CLOSE
事件。 - 每次调用则发送一次
MG_EV_POLL
事件用于让用户执行定时操作(如定时输出) (Mongoose :: Documentation)。
参数 ms
指定最多阻塞等待的毫秒数(通常为几毫秒到几百毫秒)。如果为 0,则 mg_mgr_poll()
会立即返回(非阻塞)。通常将其置于 for
或 while
无限循环中不断调用,以持续处理事件 (Mongoose :: Documentation)。例如:
while (running) {
mg_mgr_poll(&mgr, 1000); // 等待并处理事件,超时1秒
}
**事件类型:**事件回调函数的签名固定为 void fn(struct mg_connection *c, int ev, void *ev_data)
。其中 c
是发生事件的连接,ev
是事件类型代码,ev_data
是与事件相关的数据指针(不同事件类型下指向不同类型的数据) (Mongoose :: Documentation)。常见事件及其意义如下:
事件常量 | 说明 | ev_data 类型 |
---|---|---|
MG_EV_ERROR |
发生错误(连接失败等) | char * (错误消息字符串) (Mongoose :: Documentation) |
MG_EV_OPEN |
新连接创建(调用 connect/listen) | NULL (Mongoose :: Documentation) |
MG_EV_POLL |
每轮循环的空闲事件 | uint64_t * (当前运行毫秒数) (Mongoose :: Documentation) |
MG_EV_RESOLVE |
名称解析完成(DNS 解析结果) | NULL (解析成功/失败由后续事件表示) |
MG_EV_CONNECT |
连接已建立(TCP 三次握手完成) | NULL (Mongoose :: Documentation) |
MG_EV_ACCEPT |
接受到新传入连接(监听端口) | NULL (Mongoose :: Documentation) |
MG_EV_TLS_HS |
TLS 握手成功 | NULL (Mongoose :: Documentation) |
MG_EV_READ |
收到数据 | long * (读取的字节数) (Mongoose :: Documentation) |
MG_EV_WRITE |
数据已写出 | long * (写出的字节数) (Mongoose :: Documentation) |
MG_EV_CLOSE |
连接已关闭(无论正常或异常) | NULL (Mongoose :: Documentation) |
此外,不同协议还有特定的高级事件,例如:
MG_EV_HTTP_MSG
:收到了完整的 HTTP 请求(对服务端)或响应(对客户端)。ev_data
为struct mg_http_message *
(Mongoose :: Documentation)。MG_EV_WS_MSG
:收到 WebSocket 数据帧,ev_data
为struct mg_ws_message *
(Mongoose :: Documentation)。MG_EV_MQTT_MSG
:收到 MQTT 发布消息(PUBLISH),ev_data
为struct mg_mqtt_message *
(Mongoose :: Documentation)。MG_EV_MQTT_OPEN
:MQTT 连接成功(收到 CONNACK),ev_data
为int *
(CONNACK返回码) (Mongoose :: Documentation)。MG_EV_SNTP_TIME
:收到 SNTP 时间同步响应,ev_data
为uint64_t *
(UNIX时间毫秒)。MG_EV_WAKEUP
:其他线程通过mg_wakeup()
传递的数据到达,ev_data
为struct mg_str *
(参见后文) (Mongoose :: Documentation)。
在事件回调中,根据 ev
判断事件类型,并通过 ev_data
获取相关数据,编写相应的处理逻辑。例如,对 MG_EV_READ
,应用可调用 mg_send()
将 c->recv
缓冲的数据发回,实现一个简单的回显服务器 (Mongoose :: Documentation)。
连接的创建与关闭
**监听连接:**使用 mg_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data)
在指定地址上创建一个监听连接 (Mongoose :: Documentation) (Mongoose :: Documentation)。url
参数采用协议://IP:端口 的形式,例如 "tcp://0.0.0.0:1234"
表示监听 TCP 1234 端口,"udp://0.0.0.0:9000"
表示监听 UDP 9000端口 (Mongoose :: Documentation)。成功时返回 mg_connection *
指针,失败返回 NULL
(Mongoose :: Documentation)。创建后,对应的 fn
回调会收到传入连接的事件(如 MG_EV_ACCEPT
)。例子:
mg_connection *lc = mg_listen(&mgr, "tcp://0.0.0.0:1234", fn, NULL);
if (lc == NULL) {
printf("监听端口失败\n");
}
发起客户端连接:使用 mg_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data)
发起一个主动连接 (Mongoose :: Documentation)。例如 mg_connect(&mgr, "tcp://example.com:80", fn, NULL)
尝试连接远程 80 端口。该函数也是异步的:调用后立即返回一个 mg_connection *
;实际的网络连接过程在后台进行,成功建立后会向回调函数发送 MG_EV_CONNECT
事件 (Mongoose :: Documentation)。注意 url
参数可以使用协议前缀简化操作,后文 HTTP、MQTT 等模块也有各自的 connect 函数。
**数据发送:**在事件回调中,或在持有 mg_connection *
时,可使用 mg_send(c, data, size)
发送原始二进制数据 (Mongoose :: Documentation)。mg_send
将数据追加到连接的发送缓冲区,并返回成功写入缓冲的字节数 (Mongoose :: Documentation)(注意实际网络发送在稍后 poll 时完成)。对于发送文本,可使用格式化输出函数 mg_printf(c, const char *fmt, ...)
,其用法类似 printf
,能够将格式化后的字符串直接加入发送缓冲 (Mongoose :: Documentation)。例如:
mg_send(c, "Hello", 5); // 发送5字节的数据 "Hello" ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=Usage%20example%3A))
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s", 2, "OK");
// 上例构造并发送一个简单的HTTP响应头和主体 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=create%20responses%20manually%2C%20e,Length%60%20header))
Mongoose 所有发送函数(包括 mg_send
, mg_printf
等)均是非阻塞的,仅将数据存入缓冲区。等到下一次事件循环时,框架会尝试将缓冲区数据写入套接字,并在完成后触发 MG_EV_WRITE
事件通知 (Mongoose :: Documentation)。
**关闭连接:**在回调中如果需要关闭某个连接,可以设置连接对象的标志位。mg_connection
结构有一个布尔标志 is_closing
,应用可以将 c->is_closing
置为 1 来指示框架在合适的时候关闭该连接 (Mongoose :: Documentation)。一旦标记,Mongoose 将在下一个 mg_mgr_poll()
调用中关闭连接并触发 MG_EV_CLOSE
事件,然后释放相关资源 (Mongoose :: Documentation)。如果希望先发送完缓冲区再关闭,可将 c->is_draining
置1,表示发送完剩余数据后再断开。**注意:**不要直接调用系统的 close()
或 free()
来关闭连接,而应使用上述标志方式,确保资源由 Mongoose 正确管理和释放 (Mongoose :: Documentation)。
**跨线程通知:**默认情况下,Mongoose 的所有 API 不是线程安全的,必须在创建它们的同一线程内调用(即调用 mg_mgr_poll()
的线程) (Mongoose :: Documentation)。对于需要从其他线程向事件循环发送通知的情况,可使用 mg_wakeup(struct mg_mgr *mgr, unsigned long id, const void *data, size_t size)
。mg_wakeup
可以从任意线程调用,它会唤醒另一个线程中的 mg_mgr_poll()
调用,并在目标事件循环中触发一个 MG_EV_WAKEUP
事件 (Mongoose :: Documentation)。参数 id
可以指定目标连接的 ID(c->id
),从而由该连接收到事件,否则为0表示广播给事件管理器下所有连接。ev_data
将是携带的任意数据(长度为 size
) (Mongoose :: Documentation)。这对于在多线程应用中通知网络线程执行某些任务非常有用。
定时器机制
Mongoose 提供内置软件定时器,可用来在事件循环中执行周期性或延迟任务。定时器与事件管理器关联,并在 mg_mgr_poll()
中检查触发。
**创建定时器:**使用 mg_timer_add(struct mg_mgr *mgr, uint64_t period_ms, unsigned flags, void (*fn)(void *), void *fn_data)
创建一个定时器 (Mongoose :: Documentation) (Mongoose :: Documentation)。参数含义:
mgr
:事件管理器指针。period_ms
:定时间隔,单位毫秒。如果希望只执行一次,该值即延迟时间;重复执行则是周期。flags
:定时器标志位,可以是 0(默认,只执行一次)或MG_TIMER_REPEAT
(周期性重复)和/或MG_TIMER_RUN_NOW
(立即执行一次然后再按照周期) (Mongoose :: Documentation)。fn
:定时器回调函数,当定时到期时调用。fn_data
:传递给定时器函数的用户数据指针。
返回值是分配的 struct mg_timer *
指针,可用于取消定时器。**示例:**创建一个每秒触发的定时器,在回调中打印日志:
void my_timer_fn(void *arg) {
printf("定时器触发!\n");
}
mg_timer_add(&mgr, 1000 /*ms*/, MG_TIMER_REPEAT, my_timer_fn, NULL);
// 每隔1秒将调用一次 my_timer_fn ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=Usage%20example%3A))
**销毁定时器:**当不再需要时,可调用 mg_timer_free(struct mg_timer **head, struct mg_timer *t)
取消并释放定时器 (Mongoose :: Documentation)。一般传入管理器的定时器链表头(&mgr.timers
)和目标定时器指针 t
。另外,调用 mg_mgr_free()
时会自动清理所有附属定时器,因此也可不手动释放。
TCP/UDP 模块 (底层套接字)
Mongoose 可以直接用于原生的 TCP/UDP 通信,提供较薄的抽象层。通过 URL scheme 来区分使用 TCP 或 UDP。例如:
mg_listen(&mgr, "tcp://IP:port", handler, NULL)
用于监听 TCP 端口 (Mongoose :: Documentation)。mg_listen(&mgr, "udp://IP:port", handler, NULL)
用于监听 UDP 端口 (Mongoose :: Documentation)。mg_connect(&mgr, "tcp://IP:port", handler, NULL)
创建 TCP 客户端连接 (Mongoose :: Documentation)。mg_connect(&mgr, "udp://IP:port", handler, NULL)
创建 UDP 套接字并“连接”到指定远程地址(注意 UDP 无真正连接,此调用仅指定默认目标地址)。
**TCP 通信:**对 TCP 连接而言,数据通过 MG_EV_READ
和 MG_EV_WRITE
事件传递。服务器可通过监听得到 MG_EV_ACCEPT
后,每个客户端连接由独立的 mg_connection
表示。应用在 MG_EV_READ
时读取 c->recv.buf
中的数据并处理,可使用 mg_send
/mg_printf
将响应数据加入发送缓冲。除非发生错误,TCP 连接持续存在,双方可多次收发数据,直到一方关闭连接(对应另一方收到 MG_EV_CLOSE
)。
**UDP 通信:**UDP 是无连接的,Mongoose 将每个 UDP 套接字(无论通过 mg_listen
还是 mg_connect
创建)也作为 mg_connection
表示。与TCP不同:
- UDP 服务器(监听)会在
MG_EV_READ
事件中收到来自任意远端的报文。可以通过c->rem
字段(远端地址)来识别数据来源,并可能需要在响应时指定目的地址。 - 发送 UDP 数据:如果连接是由
mg_connect("udp://...")
创建的,则mg_send
会将数据发往指定的远程主机。如果是监听获得的mg_connection
(即c->is_listening=1
的UDP socket),建议不要直接用该连接发送数据。通常的做法是调用mg_connect
创建一个新的 UDP 连接指向目标地址再发送,或者使用更底层的机制设置目标地址。相对而言,对于需要收发多播/广播或一对多 UDP 通信,可以考虑为每个对端维护单独的mg_connection
。
示例:TCP Echo 服务器 – 下面的事件处理函数实现了一个简单的 TCP 回显服务器逻辑,将客户端发送的数据原样返回并在消息超过一定长度时关闭连接:
static void echo_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_READ) {
// 将收到的数据原封不动写回发送缓冲区
mg_send(c, c->recv.buf, c->recv.len);
c->recv.len = 0; // 清空已读取的数据
// 可选:如果消息太长,关闭连接以防止滥用
if (c->recv.len > 1024) {
c->is_closing = 1; // 超过长度,标记关闭 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=%2A%20%60c,or%20a%20clean%20TCP%20closure))
}
}
}
对于 UDP,处理函数类似,但可能要检查 c->rem
获取发送者信息,并在需要回复时创建新连接。例如实现“UDP 回显”,可以在 MG_EV_READ
中使用 mg_connect()
创建一个 UDP 客户端指向 c->rem
地址,然后用该连接 mg_send()
将数据发回。
HTTP 模块
HTTP 模块是 Mongoose 最常用的部分之一,包含构建 HTTP 服务器和客户端的功能。HTTP 服务器可以方便地提供嵌入式设备的 Web 控制界面或 REST API;HTTP 客户端可以用来在设备端发起 HTTP 请求(如与云服务交互)。
HTTP 服务端
**启动HTTP服务器:**使用 mg_http_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data)
在指定地址启动 HTTP 服务 (Mongoose :: Documentation)。url
通常形如 "http://0.0.0.0:8000"
(监听所有网卡的8000端口)或 "http://192.168.1.100:80"
(监听特定IP和端口) (Mongoose :: Documentation)。成功返回 mg_connection *
(监听连接)。与 mg_listen
不同,mg_http_listen
内部会自动设置HTTP协议的处理逻辑,因此传入回调 fn
会收到高级别的 HTTP 事件,而无需手动解析请求 (Mongoose :: Documentation)。例如:
mg_http_listen(&mgr, "http://0.0.0.0:80", http_fn, NULL);
如需同时支持 HTTPS,使用 "https://..."
前缀即可监听 TLS 端口(更多细节见下文 TLS 支持) (Mongoose :: Documentation)。
**HTTP 请求处理:**当客户端发来 HTTP 请求时,监听连接会产生一个新的连接 c
表示该会话,然后在用户回调中收到 MG_EV_HTTP_MSG
事件 (Mongoose :: Documentation)。ev_data
指向 struct mg_http_message
,其中包含了解析后的请求信息(请求行、头部、正文等) (Mongoose :: Documentation)。开发者可以通过该结构访问请求的方法、URI、查询参数、HTTP版本等字段,以及请求体和头部列表:
hm->method
:请求方法(如 GET、POST),类型为struct mg_str
。hm->uri
:请求的 URI 路径部分(不含查询参数)。hm->query
:URI 中的查询字符串(若有,以?
后面的部分)。hm->body
:HTTP 请求体数据。hm->headers
:HTTP 头部数组,可以通过遍历或使用辅助函数获取特定头。
例如,要判断请求 URI 是否为某接口,可以使用 mg_match(hm->uri, mg_str("/api/target"), NULL)
进行匹配 (Mongoose :: Documentation)。要读取某个头的值,可使用 mg_http_get_header(hm, "Content-Type")
,返回指向相应头值的 mg_str
(Mongoose :: Documentation)。若要提取查询参数或表单数据,可使用 mg_http_get_var(&hm->query, "name", buf, buf_len)
或 mg_http_get_var(&hm->body, "field", buf, len)
,该函数将查找参数并复制值到提供的缓冲区。
**发送HTTP响应:**Mongoose 提供多种方式发送响应:
mg_http_reply(struct mg_connection *c, int status, const char *headers, const char *body_fmt, ...)
一步构造并发送HTTP响应 (Mongoose :: Documentation)。其中status
是状态码(如200),headers
是额外头部字符串(可为""
表示无额外头部),body_fmt
是可选的响应体格式化字符串(如"%s"
)和后续参数。如果body_fmt
为 NULL 则表示仅发送头部而无正文。例如:
会发送:状态行"HTTP/1.1 404 Not Found",然后 Content-Type 头,空行,和文本"Not found"作为响应体。mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not found");
- 手动发送:使用
mg_printf
等手动发送响应行和各部分。这适用于需要逐步发送或自定义传输(如分块编码)。例如手动发送一个简单响应:
但要注意正确计算 Content-Length 或使用 chunked 编码。mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s", (int) hm->body.len, hm->body.ptr);
- 分块发送:
mg_http_printf_chunk(c, fmt, ...)
和mg_http_write_chunk(c, buf, len)
提供了发送 HTTP 分块编码(chunked transfer)的便利函数。当响应体较大或需要流式输出时,可以在发送响应头时指定"Transfer-Encoding: chunked"
,然后多次调用mg_http_printf_chunk
发送任意数量的块,最后以发送一个零长度的块表示结束 (Mongoose :: Documentation)。
**静态文件服务:**Mongoose 可以直接将本地文件或目录映射为 HTTP 服务,非常适合构建文件服务器或提供设备内置的网页。主要有两个API:
mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, const struct mg_http_serve_opts *opts)
将请求映射到指定目录下的文件。 (Mongoose :: Documentation)使用此函数时,应提供一个mg_http_serve_opts
结构,其中至少设置opts.root_dir
为要服务的本地目录路径 (Mongoose :: Documentation)。调用该函数后,Mongoose 会根据hm->uri
请求路径在指定目录查找文件。如果找到文件则自动发送文件内容(包含正确的Content-Type等),如果路径是目录则返回目录索引或错误。示例:在回调中处理其他 URI 时使用:
可选的struct mg_http_serve_opts opts = {.root_dir = "/www"}; mg_http_serve_dir(c, hm, &opts);
opts
字段还包括opts.extra_headers
(附加响应头,比如CORS设置)、opts.mime_types
(自定义扩展名到 MIME 类型映射)、opts.page404
(指定404页面文件)等。mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, struct mg_http_serve_opts *opts)
直接发送指定路径的文件作为响应 (Mongoose :: Documentation)。如果文件不存在则返回404。它类似于mg_http_serve_dir
,但用于单个文件场景,常用于将某些请求固定映射到一个文件。例如将所有对根路径的请求返回同一个 HTML 文件(单页应用的入口)。
使用上述函数时,Mongoose 内部会处理范围请求(HTTP Range)、GZIP压缩(如果请求带有 Accept-Encoding: gzip
且目录下有对应 .gz
文件,则自动发送压缩文件) (Mongoose :: Documentation)、缓存控制等常见静态服务细节,让开发者无需重复造轮子。
**HTTP 服务器完整示例:**下面代码展示了一个完整的 HTTP 服务程序,它提供两个功能:对 /api/time
请求返回当前时间的 JSON(REST API),其他路径下则提供静态文件服务(假设当前目录有网页文件)。
#include "mongoose.h"
static const char *s_root_dir = "."; // 静态文件所在目录
static void http_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/time"), NULL)) {
// 如果是 /api/time 请求,返回JSON时间
time_t now = time(NULL);
mg_http_reply(c, 200, "Content-Type: application/json\r\n",
"{\"time\": %lu}\n", (unsigned long) now);
} else {
// 其他请求当作文件访问
struct mg_http_serve_opts opts = {.root_dir = s_root_dir};
mg_http_serve_dir(c, hm, &opts);
}
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
// 监听 HTTP (8080端口) 和 HTTPS (8443端口),共用同一个事件回调
mg_http_listen(&mgr, "http://0.0.0.0:8080", http_fn, NULL);
mg_http_listen(&mgr, "https://0.0.0.0:8443", http_fn, NULL); // HTTPS 监听 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=match%20at%20L106%20mg_http_listen%28%26mgr%2C%20,%2F%2F%20Setup%20HTTPS%20listener))
printf("HTTP服务器已启动:http://localhost:8080 ,HTTPS://localhost:8443\n");
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
在这个例子中,使用 mg_http_listen
启动了两个监听,一个 HTTP、一个 HTTPS。对于 HTTPS,我们需要提供证书才能正常工作,下文会介绍如何配置 TLS。在回调中,根据 URI 区分 API 请求和文件请求,分别使用 mg_http_reply
和 mg_http_serve_dir
处理。这样,一个简单的 Web 服务就搭建完成了。
HTTP 客户端
Mongoose 也能充当 HTTP 客户端,即主动向别的服务器发出请求。典型用例如:设备启动后向云端汇报状态(HTTP POST),或者定期拉取配置。
**建立HTTP客户端连接:**使用 mg_http_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data)
创建一个 HTTP 客户端连接 (Mongoose :: Documentation)。url
可以包含 http://
或 https://
前缀以及路径,例如 "http://example.com/api"
。调用后会返回一个连接指针(或 NULL 表示失败)。连接建立和 HTTP 协议握手(发送请求)不是自动完成的,还需要在回调中进一步动作:
- 当底层TCP连接建立时,会触发
MG_EV_CONNECT
事件。这时应用可以通过mg_printf
等发送HTTP请求行和头部。 - **简化方式:**更方便的是,在调用
mg_http_connect()
的同时,利用其返回值mg_connection *
(或在MG_EV_OPEN
/MG_EV_CONNECT
事件中)调用mg_printf
发送请求。例如:
上述代码将在连接建立后立即发送一个 GET 请求。struct mg_connection *c = mg_http_connect(&mgr, "http://httpbin.org/get", client_fn, NULL); if (c) { mg_printf(c, "GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n"); }
- 当服务器响应到达且完整解析后,会触发
MG_EV_HTTP_MSG
事件,此时ev_data
中的struct mg_http_message
包含HTTP响应信息(status码、响应头、响应体等) (Mongoose :: Documentation)。应用可以检视hm->body
或使用mg_http_status(hm)
获取状态码 (Mongoose :: Documentation)。 - 读取完需要的数据后,可以关闭连接或保持连接复用(根据 HTTP Keep-Alive 情况,Mongoose 默认支持HTTP/1.1 Keep-Alive)。
**HTTP 客户端示例:**以下代码发起一个 HTTP POST 请求,将 JSON 数据发送到服务器并打印返回结果:
static void client_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
// 连接成功,发送HTTP POST请求
mg_printf(c,
"POST /api/data HTTP/1.1\r\n"
"Host: example.com\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
13, "{\"key\":\"val\"}");
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
int code = mg_http_status(hm); // 获取响应状态码 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=mg_http_status))
printf("服务器返回状态: %d, 响应体长度: %d\n", code, (int) hm->body.len);
// 打印响应体(假设是文本)
printf("响应内容:\n%.*s\n", (int) hm->body.len, hm->body.ptr);
c->is_closing = 1; // 完成后关闭连接
}
}
// 在某处调用 mg_http_connect 发起请求
mg_http_connect(&mgr, "http://example.com/api/data", client_fn, NULL);
需要注意,如果使用 https://
URL,则必须在连接建立后设置 TLS(参考下文 TLS 部分),否则无法正常完成 SSL/TLS 握手。
HTTP 认证和其它工具
Mongoose 提供了一些辅助函数便于实现 HTTP 常见功能:
- Basic Auth 认证:
mg_http_creds()
可从HTTP头中解析出 Basic Authentication 的用户名密码 (Mongoose :: Documentation);mg_http_bauth(struct mg_connection *c, const char *user, const char *pass)
则可以向HTTP请求添加 Basic Auth 的认证头 (Mongoose :: Documentation)。例如在客户端请求前调用mg_http_bauth(c, "admin", "123")
会在发送缓冲区添加Authorization: Basic ...
头 (Mongoose :: Documentation)。服务端可用mg_http_creds(hm, user_buf, userlen, pass_buf, passlen)
提取出提供的用户名密码 (Mongoose :: Documentation)。 - URL 编码/解码:
mg_url_decode()
可对 URL 中的%xx
进行解码。mg_http_get_var()
则可直接获取解码后的变量值 (Mongoose :: Documentation)。 - 文件上传:
mg_http_upload()
辅助实现 HTTP 文件上传(基于 multipart/form-data)。它能够将接收到的大文件分块写入磁盘,避免占用大量内存。典型用法是在接收到上传请求时反复调用mg_http_upload(c, hm, &mg_fs_posix, "/upload_dir", MAX_SIZE)
。该函数会将文件数据写入/upload_dir
下相应文件,并自动处理分块 (Mongoose :: Documentation)。 - **Web 表单解析:**对于
multipart/form-data
类型,mg_http_next_multipart()
可逐个解析表单部件 (Mongoose :: Documentation)。调用时传入请求体和偏移,不断迭代获取每个mg_http_part
(包含 part 的 name、filename、body 等) (Mongoose :: Documentation)。
通过这些工具函数,可以方便地扩展 HTTP 服务端的能力,如增加身份验证、处理文件上传下载等。
WebSocket 模块
WebSocket 是在单个 TCP 连接上进行全双工通讯的协议,常用于网页与设备间的实时数据交换。Mongoose 对 WebSocket 提供了简单的支持,包括服务器端的升级握手和客户端的连接。
WebSocket 服务器
WebSocket 握手是基于 HTTP 的升级过程。Mongoose 的设计是:先使用 mg_http_listen()
提供 HTTP 服务,然后在收到特定 HTTP 握手请求时,调用 mg_ws_upgrade()
切换协议,使该连接进入 WebSocket 模式 (Mongoose :: Documentation) (Mongoose :: Documentation)。流程如下:
- HTTP 握手请求:客户端发送带有
Upgrade: websocket
头的 HTTP GET 请求。Mongoose 解析后在回调中产生MG_EV_HTTP_MSG
事件。应用需要检测这是否为 WebSocket 请求。通常可以检查hm->headers
中的"Upgrade"
和"Sec-WebSocket-Key"
等头是否存在。 - 执行协议升级:调用
mg_ws_upgrade(c, hm, extra_headers_fmt, ...)
完成握手响应 (Mongoose :: Documentation)。c
是当前连接,hm
是收到的HTTP请求。可选的extra_headers_fmt
可传入额外的响应头(一般传 NULL)。成功调用后,Mongoose 会发送符合 WebSocket 协议的101状态响应并完成握手 (Mongoose :: Documentation)。此时该连接的协议处理器切换为 WebSocket 模式,随后应用回调将改为接收 WebSocket 事件而非 HTTP 事件。 - 握手完成事件:一旦升级完成,用户回调会收到
MG_EV_WS_OPEN
事件(表示 WebSocket 握手成功,可以开始通信) (Mongoose :: Documentation)。ev_data
为struct mg_http_message *
,即初始的握手HTTP请求,可以从中获取 WebSocket 子协议等信息。 - 数据通信:接下来,服务器与客户端可相互发送消息。收到消息时触发
MG_EV_WS_MSG
事件,ev_data
为struct mg_ws_message *
(Mongoose :: Documentation)。mg_ws_message
结构包含:data
:消息内容(一个mg_str
,可包括文本或二进制数据) (Mongoose :: Documentation)。flags
:标志位,最低4位表示 WebSocket opcode(消息类型) (Mongoose :: Documentation)。常用的操作码有:WEBSOCKET_OP_TEXT
(1) 文本消息、WEBSOCKET_OP_BINARY
(2) 二进制消息、WEBSOCKET_OP_CLOSE
(8) 关闭帧、WEBSOCKET_OP_PING
(9) Ping、WEBSOCKET_OP_PONG
(10) Pong (Mongoose :: Documentation)。 应用可以检查wm->flags & 0x0F
来判断消息类型 (Mongoose :: Documentation) (Mongoose :: Documentation)。通常,对于文本或二进制消息,取出wm->data
处理内容;对于 Ping 可以回复 Pong(Mongoose 可能已自动处理Ping/Pong,但应用可选择处理)。
- **发送消息:**使用
mg_ws_send(struct mg_connection *c, const void *buf, size_t len, int op)
发送 WebSocket 消息 (Mongoose :: Documentation)。其中op
是操作码,例如WEBSOCKET_OP_TEXT
或WEBSOCKET_OP_BINARY
等。该函数会按照 WebSocket 格式封装数据帧并加入发送缓冲,返回发送的字节数 (Mongoose :: Documentation)。也可以使用mg_ws_printf(c, op, fmt, ...)
直接格式化发送文本 (Mongoose :: Documentation)。发送后,对端会收到相应的MG_EV_WS_MSG
事件。 - **关闭连接:**当需要关闭 WebSocket 时,可以调用
mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE)
发送关闭帧,或者直接设置c->is_closing
关闭TCP连接(对端会相应收到关闭事件)。
**WebSocket 服务器示例:**下面示例程序基于 HTTP 服务器升级 WebSocket,在任何 WebSocket 连接上实现echo功能(收到消息后原样发送回去)。
#include "mongoose.h"
// WebSocket和HTTP共用的事件处理函数
static void ws_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
// 收到HTTP请求,检查是否为WebSocket握手
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/ws")) {
// 如果URI匹配 /ws,则执行WebSocket协议升级
mg_ws_upgrade(c, hm, NULL); // 升级为WebSocket ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=%2F%2F%20Mongoose%20events%20handler%20void,))
} else {
// 非 /ws 请求,返回404
mg_http_reply(c, 404, "", "Not found\n");
}
} else if (ev == MG_EV_WS_OPEN) {
// WebSocket 握手成功
printf("客户端 %p WebSocket 已连接\n", c);
// 可选:发送一条欢迎消息
mg_ws_send(c, "welcome", 7, WEBSOCKET_OP_TEXT);
} else if (ev == MG_EV_WS_MSG) {
// 收到 WebSocket 数据帧
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
// 将消息内容再发回给客户端(原样echo)
mg_ws_send(c, wm->data.ptr, wm->data.len, WEBSOCKET_OP_TEXT);
} else if (ev == MG_EV_CLOSE) {
if (c->is_websocket) { // 仅在WebSocket连接关闭时打印
printf("客户端 %p WebSocket 连接关闭\n", c);
}
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
// 监听 HTTP 端口并采用 ws_fn 处理,其中包含 WebSocket 升级逻辑
mg_http_listen(&mgr, "http://0.0.0.0:8000", ws_fn, NULL);
printf("WebSocket服务器已在 ws://localhost:8000/ws 等待连接...\n");
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
}
在此程序中,HTTP 请求被限制为只有路径为 /ws
的才升级为 WebSocket,其它路径返回 404。当然实际应用中可以根据需要调整路径和处理逻辑。一旦 WebSocket 连接建立,就可以在 MG_EV_WS_MSG
中处理消息。在上例中,实现了最简单的回显功能。
WebSocket 客户端
WebSocket 客户端的使用类似 HTTP 客户端。Mongoose 提供 mg_ws_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data, const char *fmt, ...)
函数来发起 WebSocket 连接 (Mongoose :: Documentation)。参数 url
形如 "ws://echo.server.com/chat"
或 "wss://..."
(HTTPS的WebSocket)等。可选的 fmt
参数可用于指定额外的握手头部,比如指定 Sec-WebSocket-Protocol
子协议等 (Mongoose :: Documentation)。
mg_ws_connect()
调用后会立即返回一个 mg_connection *
或 NULL(错误)。连接建立过程发生在后台:
- TCP 连接建立成功时会触发
MG_EV_CONNECT
。 - 紧接着 Mongoose 会发送 WebSocket 握手请求(HTTP GET 带 Upgrade: websocket)。无需应用手动发送,
mg_ws_connect
已经完成了此步骤。 - 收到服务器的握手应答(101 切换协议)后,触发
MG_EV_WS_OPEN
,表示 WebSocket 通道建立 (Mongoose :: Documentation)。 - 之后通信过程与服务器端相同,会收到
MG_EV_WS_MSG
等事件。
使用 WebSocket 客户端需要注意:如果 url
使用了 wss://
(TLS),则需要在 MG_EV_CONNECT
事件中调用 mg_tls_init()
设置 TLS(与HTTPS客户端类似)。
**WebSocket 客户端示例:**以下代码连接到一个公共 echo WebSocket 服务,发送一条消息并打印回显:
static const char *s_ws_url = "ws://echo.websocket.events"; // 公共测试WebSocket
static void ws_client_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_WS_OPEN) {
printf("WebSocket 连接建立成功!发送测试消息...\n");
mg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT);
} else if (ev == MG_EV_WS_MSG) {
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
printf("收到服务回显: %.*s\n", (int) wm->data.len, wm->data.ptr);
c->is_closing = 1; // 收到一次回显后关闭
} else if (ev == MG_EV_CLOSE) {
printf("WebSocket 客户端连接已关闭\n");
}
}
// 发起WebSocket连接
mg_ws_connect(&mgr, s_ws_url, ws_client_fn, NULL, NULL);
在 MG_EV_WS_OPEN
中,我们知道握手成功,可以安全地调用 mg_ws_send
发送文本。这段代码将在收到服务端回显后关闭连接。
MQTT 模块
MQTT 是轻量级发布/订阅协议,广泛用于物联网。Mongoose 对 MQTT 提供了内置支持,包括 MQTT 客户端和服务端(Broker)。主要接口封装在 mg_mqtt_*
系列函数中。
MQTT 客户端
**建立MQTT连接:**使用 mg_mqtt_connect(struct mg_mgr *mgr, const char *url, const struct mg_mqtt_opts *opts, mg_event_handler_t fn, void *fn_data)
来连接到 MQTT 服务器 (Mongoose :: Documentation)。url
可以是 "mqtt://broker-address:port"
,若使用 TLS 则 "mqtts://..."
。opts
是一个包含MQTT连接参数的结构,常用字段有:
opts.client_id
:客户端标识(若留空,服务器可能分配随机ID)。opts.user
/opts.pass
:用户名和密码(用于鉴权,选填) (Mongoose :: Documentation)。opts.clean
:布尔值,是否要求清除会话(类似 MQTT clean session 标志) (Mongoose :: Documentation)。opts.keepalive
:保持存活时间(秒),不发送消息时的心跳间隔 (Mongoose :: Documentation)。opts.will_topic
/opts.will_message
:遗嘱消息的主题和内容(可选,在客户端异常断开时由服务器发布)。
mg_mqtt_connect()
调用后即返回连接指针或 NULL(错误)。和 HTTP 类似,它只是初始化连接,真正的操作在事件中:
- 当底层TCP建立后,产生
MG_EV_CONNECT
事件 (Mongoose :: Documentation)。Mongoose 随即会发送 MQTT Connect 报文(包含 opts 中的参数),不需要用户手动构造。事实上,mg_mqtt_connect
内部已经根据提供的opts
构建并发送了 CONNECT 消息 (Mongoose :: Documentation)。 - 当收到服务器的 CONNACK 响应后,产生
MG_EV_MQTT_OPEN
事件,表示MQTT握手成功建立 (Mongoose :: Documentation)。ev_data
指向一个int
,表示 CONNACK 返回码(0为连接成功,非0表示失败原因) (Mongoose :: Documentation)。 - 此后便可订阅/发布主题。
**订阅主题:**使用 mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts)
订阅主题 (Mongoose :: Documentation)。需要在 opts
中设置 .topic
字段为要订阅的主题名,以及 .qos
为QoS等级(0、1或2) (Mongoose :: Documentation)。调用后Mongoose会发送 SUBSCRIBE 报文。可通过监听 MG_EV_MQTT_CMD
事件来确认订阅是否成功(它在收到 SUBACK 时触发)。
**发布消息:**使用 mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts)
发布消息 (Mongoose :: Documentation)。opts
中需设置 .topic
为目标主题,.message
为消息内容,.qos
和 .retain
(是否保留消息)根据需要设置 (Mongoose :: Documentation)。函数返回一个 uint16_t
报文标识(MQTT PUBLISH报文的ID) (Mongoose :: Documentation)。注意QoS 0不需要ACK,QoS1需要等待PUBACK事件(对应 MG_EV_MQTT_CMD
),QoS2更复杂(PUBREC/PUBREL/PUBCOMP)。
**接收消息:**当订阅的主题有消息发布时,客户端会收到 MG_EV_MQTT_MSG
事件 (Mongoose :: Documentation)。ev_data
是 struct mg_mqtt_message *
,包含此次 PUBLISH 的详细信息 (Mongoose :: Documentation)。常用字段:
mm->topic
:消息主题(mg_str)。mm->data
:消息内容(mg_str)。mm->qos
:QoS等级。mm->retain
:是否为保留消息(在 mg_mqtt_message 里可能通过 flags 或其他方式表示)。 应用应根据需要处理该消息,例如将其内容解析后采取动作。
**断开连接:**可以调用 mg_mqtt_disconnect(c, opts)
来发送 DISCONNECT 报文并关闭连接 (Mongoose :: Documentation)。实际上直接关闭TCP连接也会由服务器检测断开,但发送DISCONNECT可以更优雅地告知服务器。
**MQTT 客户端示例:**下面示例连接公共测试服务器,订阅主题并收到后再发布回应:
static const char *s_mqtt_url = "mqtt://broker.hivemq.com:1883";
static struct mg_connection *s_mqtt_conn = NULL; // 保存MQTT连接全局指针
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_MQTT_OPEN) {
// MQTT 连接成功(CONNACK 收到,ev_data 是 connack 返回码)
int conn_status = *(int *) ev_data;
if (conn_status == 0) {
printf("MQTT成功连接到服务器!\n");
// 订阅主题 "test/topic"
struct mg_mqtt_opts sub_opts = {.topic = mg_str("test/topic"), .qos = 1};
mg_mqtt_sub(c, &sub_opts);
printf("已订阅主题: %s\n", "test/topic");
} else {
printf("MQTT连接失败,返回码 %d\n", conn_status);
}
} else if (ev == MG_EV_MQTT_MSG) {
// 收到订阅的消息
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
printf("收到主题 [%.*s] 消息: %.*s\n",
(int) mm->topic.len, mm->topic.ptr,
(int) mm->data.len, mm->data.ptr);
// 构造回应消息,将内容加上前缀后发布到另一个主题
char msg[100];
snprintf(msg, sizeof(msg), "Echo: %.*s", (int) mm->data.len, mm->data.ptr);
struct mg_mqtt_opts pub_opts = {
.topic = mg_str("test/echo"),
.message = mg_str(msg),
.qos = 1
};
mg_mqtt_pub(c, &pub_opts);
printf("已发布响应到主题: test/echo\n");
} else if (ev == MG_EV_CLOSE) {
printf("MQTT连接已关闭\n");
s_mqtt_conn = NULL;
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
// 使用 clean session 连接
struct mg_mqtt_opts opts = {.clean = true, .client_id = mg_str("mg_demo")};
s_mqtt_conn = mg_mqtt_connect(&mgr, s_mqtt_url, &opts, mqtt_fn, NULL);
if (s_mqtt_conn == NULL) {
printf("无法创建 MQTT 连接\n");
return 1;
}
printf("连接到 MQTT broker 中...\n");
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
}
此程序连接到 test broker,订阅 "test/topic"
并将收到的消息加上前缀后发布到 "test/echo"
主题。通过该例,可以看出 MG_EV_MQTT_OPEN
和 MG_EV_MQTT_MSG
事件的处理。
MQTT 服务器(Broker)
Mongoose 也允许充当 MQTT Broker(服务器)。通过 mg_mqtt_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data)
可以在指定地址监听 MQTT 协议 (Mongoose :: Documentation)。之后在事件回调中处理 MQTT 事件:
- 新客户端连入时,会收到
MG_EV_ACCEPT
和MG_EV_MQTT_CMD
事件,其中 Connect 报文可在MG_EV_MQTT_CMD
检测(mm->cmd == MQTT_CMD_CONNECT
)。 - 可以调用
mg_mqtt_login(c, opts)
来发送 CONNACK (Mongoose :: Documentation)。 - 客户端发布消息会触发
MG_EV_MQTT_MSG
,服务器可视需要将消息转发给订阅了该主题的其他客户端。 - 订阅请求将以
MG_EV_MQTT_CMD
事件出现(MQTT_CMD_SUBSCRIBE
),可通过mg_mqtt_sub()
确认订阅(发送 SUBACK)。 - 基于以上机制,开发完整的 Broker 逻辑需要维护订阅表,将 PUBLISH 分发给匹配的连接等。
实现一个完整 MQTT Broker 较为复杂,需要处理协议细节。由于问题重点在参考手册,详细实现不展开。开发者可利用 Mongoose 提供的底层 MQTT 消息解析能力(通过 mg_mqtt_message
和各事件)实现自己的 Broker。
TLS/SSL 支持
Mongoose 在 7.x 版本中内置了一个轻量的 TLS 1.3 实现,也支持集成第三方 TLS 库(如 mbedTLS、OpenSSL)。这意味着既可以在没有外部依赖的情况下启用HTTPS/MQTTS等安全协议,也可以使用系统已有的TLS库。
启用 TLS
默认情况下,Mongoose 未启用 TLS。可通过编译选项或配置宏启用:
- **使用内置TLS实现:**编译时定义
-DMG_TLS=MG_TLS_BUILTIN
或在mongoose_config.h
中定义#define MG_TLS MG_TLS_BUILTIN
来启用自带的 TLS 1.3 支持 (Mongoose :: Documentation)。内置实现基于 ECC,非常适合嵌入式设备使用。 - **使用OpenSSL等外部库:**将
MG_TLS
设置为MG_TLS_OPENSSL
、MG_TLS_MBED
等,并确保链接对应库。比如-DMG_TLS=MG_TLS_OPENSSL
并链接-lssl -lcrypto
。 - 如果不需要TLS,可保持默认
MG_TLS_NONE
,则https://
或mqtts://
URL 将无法使用。
加载证书和密钥
无论使用哪种 TLS 实现,服务器模式通常需要提供证书和私钥,客户端模式通常需要提供 CA 证书以验证服务器。
Mongoose 将证书/密钥通过一个 struct mg_tls_opts
结构传递 (Mongoose :: Documentation):
struct mg_tls_opts {
struct mg_str ca; // CA证书(PEM或DER格式),用于验证对端证书 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=struct%20mg_str%20ca%3B%20%20,PEM%20or%20DER))
struct mg_str cert; // 自身证书(PEM或DER)
struct mg_str key; // 自身私钥(PEM或DER)
struct mg_str name; // 服务器名字,用于验证主机名(可选)
};
服务器通常设置 cert
和 key
字段(以及可能的 ca
用于双向认证) (Mongoose :: Documentation);客户端通常设置 ca
(CA 根证书,用于验证服务器),以及 name
(服务器域名,用于主机名验证) (Mongoose :: Documentation)。
**注意:**可以将 PEM 文件的内容读入内存并用 mg_str()
封装,例如:
const char *pem_cert = "-----BEGIN CERTIFICATE-----\n...";
struct mg_tls_opts opts = {
.ca = mg_str(ca_pem_data),
.cert = mg_str(cert_pem_data),
.key = mg_str(key_pem_data)
};
Mongoose 不会自行读取文件,因此需要应用层提供证书数据。
初始化 TLS 会话
对于使用 TLS 的连接(无论客户端或服务器),需要在连接建立时调用 mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts)
(Mongoose :: Documentation)。可以在以下时机调用:
- **服务器:**在收到新连接的
MG_EV_ACCEPT
事件时,调用mg_tls_init(c, &opts)
将该连接切换为 TLS 模式 (Mongoose :: Documentation)。这会触发 TLS 握手过程,并在成功后产生MG_EV_TLS_HS
事件(TLS handshake succeeded)。 - **客户端:**在检测到
MG_EV_CONNECT
事件时调用 (Mongoose :: Documentation)。例如对于https://
或mqtts://
连接,在MG_EV_CONNECT
时执行mg_tls_init(c, &opts)
开始握手。 - 也可以在调用
mg_http_listen()
或mg_mqtt_connect()
返回后立即对监听连接或主动连接调用mg_tls_init
。但通常推荐在事件回调里处理,以确保底层连接已就绪。
**示例:**HTTP+TLS 服务器为监听8443端口的连接配置证书:
// 全局加载证书和私钥(例如从文件或定义的字符串)
struct mg_tls_opts tls_opts;
tls_opts.cert = mg_str(server_cert_pem);
tls_opts.key = mg_str(server_key_pem);
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT) {
// 为每个新接受的连接初始化 TLS
mg_tls_init(c, &tls_opts); // 执行 TLS 握手 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=%2F%2F%20server%20event%20handler%3A%20if,mg_tls_init%28c%2C%20%26opts))
} else if (ev == MG_EV_HTTP_MSG) {
// ... HTTP请求处理 ...
}
}
// 创建HTTPS监听
mg_connection *lc = mg_http_listen(&mgr, "https://0.0.0.0:8443", fn, NULL);
if (lc == NULL) {
printf("TLS监听端口失败\n");
}
客户端例如访问 HTTPS:
struct mg_tls_opts client_tls = {.ca = mg_str(my_ca_pem)};
static void client_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
mg_tls_init(c, &client_tls); // 为客户端连接设置CA证书验证 ([Mongoose :: Documentation](https://mongoose.ws/documentation/#:~:text=Usage%20example%3A))
} else if (ev == MG_EV_HTTP_MSG) {
// ... 处理HTTP响应 ...
}
}
mg_http_connect(&mgr, "https://example.com/path", client_fn, NULL);
以上在 MG_EV_CONNECT
阶段调用了 mg_tls_init
,这样后续通信都在TLS之上进行了。
TLS 选项与注意事项
- **双向认证:**如果需要客户端证书认证,在服务器端设置
opts.ca
(用于验证客户端证书的CA),并在客户端设置自己的opts.cert
和opts.key
(客户端证书和密钥)。当两端都提供证书时,即启用了双向 TLS(Mutual TLS) (Mongoose :: Documentation)。 - RNG:Mongoose 内置 TLS 使用
mg_random()
作为随机数生成器。如需自定义随机源,可定义MG_ENABLE_CUSTOM_RANDOM=1
并提供自定义的mg_random()
实现 (Mongoose :: Documentation)。 - 证书验证:客户端默认会验证服务器证书是否由给定 CA 签发以及是否匹配主机名(通过
opts.name
指定或通过所连主机自动匹配)。如果未设置 CA(opts.ca
为空),则不会验证证书 (Mongoose :: Documentation)(不安全)。开发中可临时不设CA跳过验证,但正式产品请务必配置正确的 CA 证书或证书链,以防中间人攻击。 - SNI:如客户端连接需要 Server Name Indication,可在
opts.name
中提供期望的主机名;服务器端若使用通配证书,可通过此字段获知客户端请求的域名(不过 Mongoose 未暴露回调获取 SNI名称的接口,仅用于验证)。
总之,在嵌入式环境中,Mongoose TLS 提供了免外部依赖的安全通信能力,但也允许灵活切换到成熟的TLS库,在调试和安全需求更高时使用。
嵌入式系统与 RTOS 适配
Mongoose 旨在在各类嵌入式平台上轻松运行,其架构对操作系统和网络栈具有良好适应性 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。下面概述在常见嵌入式环境下的使用注意事项:
- **裸机 (Bare Metal):**在无操作系统的微控制器上,可使用 Mongoose 提供的内置 TCP/IP 协议栈和驱动。需在编译时启用
MG_ENABLE_TCPIP=1
(Mongoose :: Documentation)并选择对应的 MAC/PHY 驱动(例如MG_ENABLE_DRIVER_STM32
等)。Mongoose 内置支持了一些常见芯片的以太网外设和独立网络芯片(如 Wiznet W5500) (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。这样可以不依赖 lwIP 等外部网络库,实现轻量网络功能。例如在 STM32 上,使用内置栈可以用极少的文件实现一个 Web 服务 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。当然,使用内置栈意味着 Mongoose 接管网络中断等,需要适配具体平台的底层接口(Mongoose已实现部分芯片的驱动)。 - **RTOS + 自带协议栈:**许多 RTOS(如 FreeRTOS 结合 lwIP,Zephyr 自带网络栈等)提供了套接字或类BSD Socket接口。Mongoose 可以直接在这些环境上运行,将
MG_ARCH
设置为相应值(例如 FreeRTOS+lwIP 可定义MG_ARCH=MG_ARCH_FREERTOS
,Zephyr 可定义MG_ARCH=MG_ARCH_ZEPHYR
等,在arch.h
中有枚举 (raw.githubusercontent.com))。如果 RTOS 已有线程调度,可以选择在一个单独的任务中运行mg_mgr_poll()
循环,使其不阻塞其它任务。注意 Mongoose 的 API 要在同一个任务中调用,其他任务若需要操作连接,可用mg_wakeup
通知。 - **ESP32 (FreeRTOS + lwIP):**ESP32 的 IDF 环境自带 FreeRTOS 和 lwIP。Mongoose 可被编译为 ESP-IDF 的组件,或直接集成源码。一般将
MG_ARCH=MG_ARCH_ESP32
(在 mongoose.h 中已有针对ESP32的适配宏定义 (raw.githubusercontent.com) (raw.githubusercontent.com))。ESP32 默认使用 FreeRTOS 底层,也可以开启 Mongoose 内置栈通过 SPI驱动兼容 Wi-Fi 模块,但通常没必要。使用时需注意 ESP32 的日志机制:可以重定向 Mongoose 的日志输出到 IDF 日志(通过实现mg_log_set_fn()
自定义打印,或利用 ESP32 自动把printf
输出转向UART的特性 (Mongoose :: Documentation))。 - **STM32 + FreeRTOS + lwIP:**在这种常见组合下,有两种选择:1)使用 lwIP 套接字接口:将
MG_ARCH=MG_ARCH_UNIX
或MG_ARCH_FREERTOS
均可,只要 lwIP 的 BSD socket API 可用。这样 Mongoose 所有网络操作走 lwIP 的接口,不需要特殊改动。需要确保在一个线程调用mg_mgr_poll()
并在有数据时调用,这通常通过 FreeRTOS线程或主循环配合sys_check_timeouts()
来处理 lwIP 定时器。2)使用内置栈 + 驱动:关闭 lwIP,启用MG_ENABLE_TCPIP
以及相应驱动宏(如MG_ENABLE_DRIVER_STM32
)。然后提供网卡底层初始化(MAC地址配置、PHY引脚等)。Mongoose 将直接控制以太网外设进行收发。对于资源受限或快速启动项目,这方案很有吸引力 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。官方提供了针对 STM32 的 CubeIDE 和 Keil 示例,可以参考。 - **RTOS 定时器与集成:**如果希望将 Mongoose 集成进现有事件循环或定时器系统,也可以每隔一定时间调用一次
mg_mgr_poll(mgr, 0)
非阻塞地处理事件。比如在系统主循环或RTOS心跳里调用。只要调用足够频繁(小于网络响应要求的间隔),也能正常运行。在硬实时系统里,这种用法可以避免一个专门线程。 - **内存与性能:**嵌入式系统通常RAM紧张。可以根据需要调整
MG_IO_SIZE
(默认8192)来改变收发缓冲区大小 (Mongoose :: Documentation);禁用不需要的协议(通过宏,如不需要 MQTT 则可以禁用MG_ENABLE_MQTT减小体积等)。Mongoose 本身非常小巧,在不开启高级协议时约50KB左右代码大小 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)。另外,可通过MG_ENABLE_HEXDUMP
控制调试功能,防止因调试日志占用大量空间。 - **多任务并发:**如果必须在多个任务分别处理网络,可为每个任务创建独立的
mg_mgr
实例各自调用mg_mgr_poll
。但要确保不同任务的 Mongoose 实例使用不同套接字或端口,且任务间完全隔离。一般推荐集中在单任务处理网络,这样维护一个连接表更简单,也避免多个网络栈实例。
**官方嵌入式示例:**Mongoose 官方网站提供了大量针对嵌入式平台的示例和向导。例如针对 ESP32、STM32(CubeIDE, Keil)、Zephyr RTOS 等都有现成项目,可以参考快速上手 (Mongoose :: Documentation) (Mongoose :: Documentation)。这些示例展示了如何配置 Mongoose 在特定平台运行,包括网卡初始化、与系统启动的结合等,对于开发者是很好的参考。
常见问题与解答
Q1: 为什么我的事件回调没有收到数据/事件?
A1: 最常见的原因是遗漏调用 mg_mgr_poll()
或调用频率不足。确保在主循环中不断调用 mg_mgr_poll()
,否则网络事件无法触发 (Mongoose :: Documentation)。另一个可能原因是监听或连接未成功创建(mg_listen
/mg_connect
返回了 NULL)。此时可以检查返回值并使用 MG_EV_ERROR
事件调试错误信息 (Mongoose :: Documentation)。
Q2: 编译时提示 “MG_ARCH 未定义,无法猜测” 错误怎么办?
A2: 这是因为 Mongoose 无法自动确定目标架构。解决办法是在编译选项加入适当定义,例如Linux环境定义 -DMG_ARCH=MG_ARCH_UNIX
,FreeRTOS 上定义 -DMG_ARCH=MG_ARCH_FREERTOS
等 (raw.githubusercontent.com) (raw.githubusercontent.com)。或者建立 mongoose_config.h
手动定义 #define MG_ARCH MG_ARCH_CUSTOM
并根据平台包含所需头文件。大多数情况下,Mongoose 会根据宏自动选择,如 _WIN32
对应 WIN32,__unix__
对应 UNIX (raw.githubusercontent.com)。但某些嵌入式编译器可能没有这些宏,需要手动指定。
Q3: 我的 HTTP 服务无法处理超过一定大小的 POST 数据,该如何解决?
A3: 默认情况下,Mongoose 的接收缓冲区大小为 MG_IO_SIZE
(默认8KB)。如果POST数据超过此大小且没有分块传输,可能需要增大缓冲区以一次性容纳完整请求,或者使用 mg_http_upload()
将数据分块写入文件。你可以通过在编译定义例如 -DMG_IO_SIZE=16384
来增大缓冲区到16KB。但务必考虑嵌入式内存限制。如果数据非常大(如上传固件几MB),最佳实践是使用 HTTP 分块传输并在应用层逐块处理,这样不需要大的内存缓存。
Q4: 为什么我的嵌入式TLS服务器浏览器访问时不被信任?
A4: 很可能是因为使用了自签名证书。浏览器会警告不受信任,这是正常的。为了解决,需使用受信任CA颁发的证书,或者在浏览器中安装自签名证书的CA。另一个可能原因是未正确配置证书链,浏览器无法验证完整链。如果是测试环境可以临时跳过,但生产环境建议获取有效证书。
Q5: 如何优雅地关闭服务器并断开所有客户端?
A5: 可以调用 mg_mgr_free(&mgr)
来关闭管理器 (Mongoose :: Documentation)。该函数内部会关闭所有连接并释放资源。如果希望逐步通知客户端,可以先发送关闭通知(例如WebSocket发关闭帧,或HTTP长连接发一个指定头等),然后稍微延时再调用 mg_mgr_free
。由于 Mongoose 是非阻塞的,没有类似 mg_close_immediately
之外的接口,标记关闭也是在下一次 mg_mgr_poll
时执行,因此常用做法是在需要停止时:先停止接受新连接(可以调用 mg_listen
返回的监听连接的 c->is_closing=1
停止监听socket),然后等待一段时间或立即调用 mg_mgr_free
。
Q6: UDP 广播/多播怎么实现?
A6: 可以使用 Mongoose 的 UDP 支持来实现。比如发送广播:用 mg_connect(&mgr, "udp://255.255.255.255:port", fn, NULL)
获取 UDP 连接,然后 mg_send()
数据即可。接收广播则用 mg_listen(&mgr, "udp://0.0.0.0:port", fn, NULL)
监听对应端口,所有广播包会触发 MG_EV_READ
。对于多播,暂时需要自行加入多播组:可以在建立 UDP socket 后,通过 setsockopt
(如果在支持BSD socket的平台) 加入组。目前 Mongoose 未封装多播组加入接口,可能需要在 MG_EV_OPEN
时获取底层 socket 描述符 (c->fd
) 执行系统调用。若使用内置栈,可能需要额外实现多播功能(7.x版本内置栈对多播支持有限)。
Q7: Mongoose 可以同时处理多少连接?
A7: Mongoose 本身没有硬编码连接数上限,受限于系统资源(内存、FD数等)。每个 mg_connection
大小大约几百字节,加上缓冲区。嵌入式设备内存小,能处理的并发连接有限,但在 PC 上上千连接也是可以的。需要注意的是默认配置下每个连接都有8K缓冲区,所以1000连接可能占用约8MB内存。可以调小 MG_IO_SIZE
或根据应用模式(比如大部分闲置连接不需要大缓冲)来自行优化。事件循环每次遍历所有连接,若连接数非常多可以考虑分成多个 mg_mgr
线程以减少单线程负担,但一般几千级别连接单线程事件模型仍能应付。
调试技巧
调试网络应用可能较为复杂,Mongoose 提供了一些辅助手段:
- **日志级别控制:**使用
mg_log_set(const char *level_spec)
调整日志级别 (Mongoose :: Documentation) (Mongoose :: Documentation)。日志分为 ERROR, INFO, DEBUG, VERBOSE 四级。调用例如mg_log_set("3")
将全局日志级别设为 DEBUG(0=NONE,1=ERROR,2=INFO,3=DEBUG,4=VERBOSE) (Mongoose :: Documentation)。也可以针对特定源文件设置不同级别,例如:mg_log_set("2,mongoose.c=4")
表示全局INFO级,mongoose.c文件输出VERBOSE级别 (Mongoose :: Documentation)。缺省日志通过printf
输出,可在编译时重定向printf
,或者使用mg_log_set_fn(mg_pfn_t fn, void *param)
提供自定义日志函数,将日志字符流发送到UART、文件或其它系统日志 (Mongoose :: Documentation) (Mongoose :: Documentation)。 - **调试输出 (Hexdump):**如果定义编译选项
MG_ENABLE_HEXDUMP=1
(默认开启),Mongoose 会在日志中输出每个TCP连接的收发数据的十六进制转储,非常有助于定位协议问题。可以在初始化时调用mg_hexdump(c->recv.buf, c->recv.len)
手动输出缓冲区内容 (Mongoose :: Documentation) (Mongoose :: Documentation)。或者设置任意连接的c->is_hexdumping = 1
,则该连接后续所有数据I/O都会以hex形式打印日志(需DEBUG级日志有效)。 - **事件追踪:**通过在回调中打印
ev
及相关数据,可跟踪状态机。由于MG_EV_POLL
过于频繁,通常忽略,仅关心 READ/WRITE/CONNECT/CLOSE 等。MG_EV_ERROR
提供了错误信息字符串,可在调试时打印出来定位问题 (Mongoose :: Documentation)。 - **Wireshark 分析:**对于标准协议如 HTTP/MQTT,借助抓包工具Wireshark也是必要手段。在PC上直接抓本机流量,在嵌入式上可以通过串口输出(hex dump)或将设备网络接入PC以桥接方式抓取。Mongoose hex dump 输出可通过
xxd -r
转换为二进制再让 Wireshark 分析,或者更简单的方法是将日志拷贝,手工解析关键部分。 - **单步调试:**Mongoose 源码精简,完全可以在调试器中单步。可以设置断点在事件触发处(例如特定 ev 值),或者在
mg_http_serve_dir
等关键函数检查参数。了解一些关键内部状态如c->send.len
,c->recv.len
,c->is_connecting
等,也有助于理解执行流程。
利用以上技巧,可以较快定位多数常见问题。特别是在封闭环境中调试网络,日志和hexdump是最有效的方法。
安全建议
在嵌入式联网应用中,安全性至关重要。以下是使用 Mongoose 时的一些安全考虑:
- **及时更新版本:**Mongoose 不断发布新版本修复漏洞和改进功能(例如 Mongoose 7.x 相比老版本6.x提升了TLS支持和安全性)。请关注官方发布的版本更新日志并尽量使用最新稳定版本。当前 7.17 版在 2025 年发布 (Tags · cesanta/mongoose · GitHub)。如果条件允许,评估升级到后续主版本(如8.x),但需注意兼容性改变。
- **使用加密协议:**尽可能使用 TLS 来保护通信安全。在 HTTP 场景,尽量使用 HTTPS 替代明文 HTTP,防止敏感数据泄露。Mongoose 提供内置 TLS,嵌入式设备也应尽量加载证书以实现加密通信。如果设备算力有限难以跑TLS,可以考虑架设安全网关等方案,但敏感信息一定要加密传输。
- **验证证书与主机名:**在 TLS 客户端中,一定提供 CA 证书并验证服务器证书有效性 (Mongoose :: Documentation)。禁用验证虽方便调试但留下隐患。可通过
opts.name
开启主机名校验 (Mongoose :: Documentation),防止 MITM 攻击伪造证书。 - **身份认证:**若设备提供控制接口(HTTP API 等),应加认证机制避免未授权访问。可以使用 HTTP Basic Auth(
mg_http_creds
/mg_http_bauth
)或更安全的基于令牌的认证。至少在局域网外的接口需要密码保护或签名校验。 - **输入验证:**Mongoose 不会自动过滤应用层的数据,开发者需对收到的数据进行合理验证。例如:
- 检查 HTTP 请求的路径和参数是否符合预期,避免恶意构造输入造成缓冲区溢出或非法文件访问(
mg_http_serve_dir
默认已经处理了..
路径跳转问题,但自行操作文件时要小心)。 - 在使用
mg_http_get_var
时,提供的缓冲区长度要足够,函数会避免溢出但如果长度不够会返回 -1,需正确处理这种情况 (Mongoose :: Documentation)。 - 对于 JSON、XML 等格式数据,使用健壮的解析库或函数(Mongoose 有简易的 mg_json_* 函数,但功能有限),并验证内容逻辑正确。
- 检查 HTTP 请求的路径和参数是否符合预期,避免恶意构造输入造成缓冲区溢出或非法文件访问(
- **防御DoS攻击:**嵌入式服务器可能受到恶意大量请求或长连接拖垮。可采取:
- 限制每客户端的并发连接数。
- 设置请求超时。例如利用
mg_timer
给连接计时,超时未完成握手或未发送数据则关闭。 - 对于长连接(如 WebSocket、MQTT),定期发送心跳并监测对方响应,以断开失效连接释放资源。
- 调整缓冲区以平衡内存和性能,例如对于只发送小通知的 IoT 设备,可将
MG_IO_SIZE
调小,防止单连接占用过多RAM。
- **最小权限原则:**如果设备文件系统有敏感信息,避免将其目录暴露通过
mg_http_serve_dir
。只开放必要的文件目录。使用opts.auth_file
等机制(Mongoose 旧版本支持 .htpasswd 认证文件,这在7.x改为由应用自行实现Basic Auth)。 - **防火墙和网络隔离:**在网络架构上,为嵌入式设备部署防火墙策略。例如限制只接受特定IP范围的连接,或使用VPN将控制流量加密。即使设备Web界面本身有认证,也建议网络层做隔离,减少暴露面。
- **定期测试:**对设备的网络接口做渗透测试,例如使用 nmap、OpenSSL 工具测试TLS握手、测试HTTP的常见漏洞(XSS/SQL注入等虽然主要针对web服务器后台,但如设备直接把请求参数用于系统命令就会有风险)。确保 Mongoose 提供的服务没有明显漏洞。
综合来说,Mongoose 提供了实现网络功能的便利,但最终安全取决于使用它的应用逻辑是否严谨。通过遵循安全开发的最佳实践,可以构建一个既功能丰富又安全可靠的嵌入式联网应用。 (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket) (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)
参考资料:
- Mongoose 官方文档和用户指南 (Mongoose :: Documentation) (Mongoose :: Documentation)
- Cesanta Mongoose GitHub README (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket) (GitHub - cesanta/mongoose: Embedded web server, with TCP/IP network stack, MQTT and Websocket)
- Mongoose 7.17 API Reference (Mongoose :: Documentation) (Mongoose :: Documentation) (Mongoose :: Documentation) (Mongoose :: Documentation)等
评论区