ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Mongoose学习笔记一】简单使用:从零搭建HTTP服务与TCP/UDP通信的配置验证

【Mongoose学习笔记一】简单使用:从零搭建HTTP服务与TCP/UDP通信的配置验证 1. 从零跑通 Mongoose为什么选它做 C/C 网络入门如果你写过 C 或 C 的网络程序大概率经历过这样的场景想做一个最简单的 HTTP 服务端结果 socket、bind、listen、accept、epoll 一整套下来代码还没跑起来就已经两百行了。更别说还要同时处理 TCP 和 UDP光是状态管理就够头疼。Mongoose 这个库解决的正是这个问题——它是一个面向 C/C 的事件驱动、非阻塞网络库把 TCP、UDP、HTTP、WebSocket、MQTT 这些协议统一到一套回调模型里。你只需要初始化一个事件管理器注册一个回调函数然后在一个循环里轮询剩下的连接管理、数据收发、协议解析它都帮你做了。这篇文章面向的是刚接触 Mongoose 的 C/C 开发者或者想找一个轻量网络库做嵌入式、边缘设备、本地工具服务端的人。我会从最朴素的 HTTP 服务端开始把 Mongoose 的初始化配置、事件回调注册、编译命令完整走一遍然后用 curl 和端口监听验证结果。接着把同样的模型扩展到 TCP 和 UDP 场景让你理解 Mongoose 的事件驱动到底是怎么统一这三种通信方式的。整个过程不需要你提前懂 epoll 或 IOCP只要会写 C 函数、会用 gcc 或 cmake 就能跟下来。Mongoose 的定位很清晰单文件、无外部依赖、可裁剪。你只需要把mongoose.c和mongoose.h两个文件拷进工程就能开始用。它不强制你用它的构建系统也不要求你改现有工程结构。对于学习网络编程的人来说这种“最小侵入”的特性非常友好——你可以先跑通一个 hello world再逐步往里加业务逻辑而不是一上来就被工程配置劝退。我试过在 Linux 和 macOS 上直接 gcc 编译也在 Windows 上用 MinGW 跑过基本没有额外坑。下面就从环境准备开始一步步把第一个 Mongoose 网络程序跑起来。2. 前置准备获取 Mongoose 源码与最小工程结构在写代码之前先把工程目录搭好。Mongoose 的获取方式很简单直接从 GitHub 克隆或者下载压缩包都行。我习惯用 git clone方便后续看它的例程和文档。git clone https://github.com/cesanta/mongoose.git克隆完成后你会看到mongoose.c和mongoose.h就在仓库根目录。这两个文件就是全部核心不需要额外安装任何库。接下来建一个自己的工程目录结构如下mongoose_demo/ ├── main.c ├── mongoose.c ├── mongoose.h └── html/ └── index.html把mongoose.c和mongoose.h从仓库拷到工程根目录html目录用来放静态页面。这个结构足够跑通 HTTP 服务端后面加 TCP/UDP 也不用改目录布局。关于编译方式我建议先用最直接的 gcc 命令把“能跑”这件事确认下来再考虑 cmake。gcc 编译命令如下gcc main.c mongoose.c -o mongoose_demo -I. -lpthread这里-I.表示头文件在当前目录-lpthread是因为 Mongoose 在 Linux 下会用到线程相关的系统调用。如果你在 macOS 上编译通常不需要显式加-lpthread但加上也不会报错。Windows 下用 MinGW 的话命令类似可能需要额外链接-lws2_32。注意Mongoose 默认使用 POSIX socket。如果你在 Windows 上用 MSVC需要定义MG_ARCHMG_ARCH_WIN32或者直接用 MinGW。初学者建议先在 Linux/macOS 上跑通减少环境变量干扰。工程结构确认后就可以开始写第一个 HTTP 服务端了。核心思路是初始化事件管理器 → 创建 HTTP 监听 → 进入事件循环 → 在回调里处理请求。下面一节会把完整代码和每个参数的含义讲清楚。3. 可复制配置HTTP 服务端初始化与事件回调注册先给出一份可以直接复制运行的main.c。这份代码在官方例程基础上做了简化去掉了不必要的分支只保留最核心的 HTTP 监听和静态文件服务方便你逐行理解。#include mongoose.h static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev MG_EV_HTTP_MSG) { struct mg_http_message *hm (struct mg_http_message *) ev_data; if (mg_http_match_uri(hm, /api/hello)) { mg_http_reply(c, 200, Content-Type: application/json\r\n, {%m:%d}\n, mg_print_esc, 0, status, 1); } else { struct mg_http_serve_opts opts {.root_dir html}; mg_http_serve_dir(c, hm, opts); } } } int main(int argc, char *argv[]) { struct mg_mgr mgr; mg_mgr_init(mgr); mg_http_listen(mgr, http://0.0.0.0:8000, fn, mgr); for (;;) mg_mgr_poll(mgr, 1000); mg_mgr_free(mgr); return 0; }这段代码里有几个关键点需要展开。struct mg_mgr mgr是事件管理器它内部维护了一个活动连接链表所有连接的状态都挂在这个结构上。mg_mgr_init(mgr)做三件事把连接链表置空、设置 IPv4/IPv6 的默认 DNS、设置 DNS 解析超时。你不需要手动填这些字段初始化函数已经给了合理默认值。mg_http_listen创建 HTTP 监听器参数依次是事件管理器、监听地址、回调函数、回调透传数据。地址http://0.0.0.0:8000表示监听本机所有网卡的 8000 端口。回调函数fn是每个连接事件发生时被调用的地方fn_data这里传的是mgr方便在回调里访问管理器。回调里的MG_EV_HTTP_MSG是 HTTP 消息到达事件。ev_data指向struct mg_http_message里面包含 method、uri、query、headers、body 等字段。mg_http_match_uri用来匹配请求路径支持 glob 模式。匹配到/api/hello时用mg_http_reply返回一段 JSON否则用mg_http_serve_dir从html目录提供静态文件。mg_http_serve_opts这个结构体控制静态文件服务的行为root_dir是 Web 根目录必须非空。你还可以设置ssi_pattern、extra_headers、mime_types、page404等字段但入门阶段只需要root_dir。事件循环for (;;) mg_mgr_poll(mgr, 1000)是 Mongoose 的驱动核心。mg_mgr_poll每次调用会做四件事检查是否有传入数据并触发MG_EV_READ、检查发送缓冲区并触发MG_EV_WRITE、如果是监听连接则接受新连接并触发MG_EV_ACCEPT、最后触发MG_EV_POLL。参数1000是超时毫秒数表示最多阻塞 1 秒等待事件。如果你更习惯用 cmake 管理工程下面这份CMakeLists.txt可以直接用cmake_minimum_required(VERSION 3.16) project(mongoose_demo C) add_executable(mongoose_demo main.c mongoose.c ) target_include_directories(mongoose_demo PRIVATE .) target_link_libraries(mongoose_demo PRIVATE pthread)这份配置和 gcc 命令等价只是把源文件、头文件路径、链接库显式声明出来。编译时执行mkdir build cd build cmake .. make生成的mongoose_demo可执行文件就可以直接运行了。下一节会用它做实际验证。4. 验证请求curl 测试与端口监听确认成功结果代码编译通过后先别急着开浏览器。用 curl 做命令行验证能更清楚地看到 HTTP 响应头和状态码排查问题也更方便。启动服务端./mongoose_demo程序会阻塞在事件循环里终端不会输出任何内容这是正常的。另开一个终端先用ss或netstat确认 8000 端口处于监听状态ss -tlnp | grep 8000你应该能看到类似这样的输出LISTEN 0 128 0.0.0.0:8000 0.0.0.0:* users:((mongoose_demo,pid12345,fd3))这说明监听已经生效。接着用 curl 请求/api/hellocurl -i http://127.0.0.1:8000/api/hello预期返回HTTP/1.1 200 OK Content-Type: application/json Content-Length: 14 {status:1}状态码 200、Content-Type 是 application/json、body 是{status:1}三个都对上就说明动态路由工作正常。再请求根路径curl -i http://127.0.0.1:8000/如果html/index.html存在会返回这个文件的内容如果不存在Mongoose 会返回 404。你可以先在html目录下建一个最简单的index.html!DOCTYPE html html head meta charsetutf-8 titlemongoose test/title /head body bfont colorbluehello world/font/b /body /html再次 curl 根路径就能看到 HTML 内容。浏览器里输入http://127.0.0.1:8000/也会显示同样的页面。到这里HTTP 服务端的完整链路就验证完了端口监听 → 请求到达 → 回调匹配 URI → 返回响应。接下来把同样的模型扩展到 TCP 和 UDP你会看到 Mongoose 的事件驱动机制在三种协议下是一致的只是监听函数和事件类型不同。5. 常见错误排查401、local proxy failed 与 reading choices 报错入门阶段最容易卡住的不是代码逻辑而是环境配置和请求方式。下面列几个我实际遇到过的报错以及对应的排查思路。报错一local proxy failed或连接被拒绝这个报错通常出现在 curl 请求时提示无法连接到 127.0.0.1:8000。原因一般是服务端没启动或者监听地址写成了http://localhost:8000而系统解析到了 IPv6 的::1但服务端只监听了 IPv4。解决办法是把监听地址改成http://0.0.0.0:8000请求时用127.0.0.1而不是localhost。另外检查防火墙是否拦截了 8000 端口。报错二reading choices或 JSON 解析失败如果你在回调里用mg_json_get解析请求体但请求体不是合法 JSON就会报类似reading choices的错误。排查时先用curl -i看请求是否真的带了 body再确认Content-Type是否正确。Mongoose 不会自动校验 JSON解析失败需要你自己在回调里判断返回值。报错三401 Unauthorized如果你在回调里加了简单的鉴权逻辑比如检查Authorization头但 curl 没带这个头就会返回 401。测试时可以用curl -i -H Authorization: Bearer test http://127.0.0.1:8000/api/hello确认鉴权逻辑本身没问题后再决定是否要在生产环境保留。报错四编译时undefined reference to pthread_create这是链接阶段缺少 pthread 库。gcc 命令加上-lpthreadcmake 里加上target_link_libraries(mongoose_demo PRIVATE pthread)。macOS 上通常不需要但加上不影响。报错五端口被占用启动时如果提示bind: Address already in use说明 8000 端口被其他进程占了。用ss -tlnp | grep 8000找到进程号kill 掉或者换一个端口比如http://0.0.0.0:8080。注意Mongoose 的错误信息默认输出到 stderr启动时如果看不到任何输出不代表出错。可以用MG_ENABLE_LOG1编译标志打开详细日志方便定位问题。排查完这些HTTP 部分基本就稳了。下面把 TCP 和 UDP 的监听方式补上让你对 Mongoose 的统一事件模型有完整认识。6. 扩展到 TCP/UDP同一套事件模型下的通信验证Mongoose 的 TCP 和 UDP 监听函数与 HTTP 类似只是事件类型和数据结构不同。先看 TCP 服务端的最小实现#include mongoose.h static void tcp_fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev MG_EV_ACCEPT) { mg_printf(c, welcome to mongoose tcp\r\n); } else if (ev MG_EV_READ) { mg_send(c, c-recv.buf, c-recv.len); c-recv.len 0; } } int main(void) { struct mg_mgr mgr; mg_mgr_init(mgr); mg_listen(mgr, tcp://0.0.0.0:9000, tcp_fn, NULL); for (;;) mg_mgr_poll(mgr, 1000); mg_mgr_free(mgr); return 0; }mg_listen是通用监听函数协议由 URL 前缀决定。tcp://创建 TCP 监听udp://创建 UDP 监听。TCP 回调里MG_EV_ACCEPT在新连接建立时触发MG_EV_READ在收到数据时触发。c-recv.buf和c-recv.len分别是接收缓冲区和长度处理完后把recv.len置零表示消费完毕。UDP 的实现略有不同因为 UDP 是无连接的static void udp_fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev MG_EV_READ) { mg_send(c, c-recv.buf, c-recv.len); c-recv.len 0; } } int main(void) { struct mg_mgr mgr; mg_mgr_init(mgr); mg_listen(mgr, udp://0.0.0.0:9001, udp_fn, NULL); for (;;) mg_mgr_poll(mgr, 1000); mg_mgr_free(mgr); return 0; }UDP 没有MG_EV_ACCEPT直接在MG_EV_READ里回显数据即可。验证 TCP 可以用ncnc 127.0.0.1 9000连接后会收到welcome to mongoose tcp输入任意字符会原样返回。验证 UDP 用echo hello udp | nc -u 127.0.0.1 9001同样会收到回显。到这里HTTP、TCP、UDP 三种场景都跑通了而且它们共用同一个mg_mgr和mg_mgr_poll循环。你完全可以在一个程序里同时监听 HTTP 8000、TCP 9000、UDP 9001只需要多调用几次mg_http_listen和mg_listen。如果你后续想把模型对话、代码生成这类能力接进自己的 C/C 工具链可以通过 API 方式调用Base URL 用https://taotoken.net/api在 API Keys 页面生成密钥后用标准 HTTP 客户端请求即可。Mongoose 本身也支持 HTTP 客户端模式mg_http_connect就能发起请求适合把本地服务和远端模型能力串起来。长期做编码或 Agent 类项目的话可以看看 Coding Plan按需选用。接入细节在 接入文档 里有完整说明模型列表和调试可以直接在 模型对话 里试。
返回列表