
嵌入式网络通信物联网【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址https://gitcode.com/gh_mirrors/mon/mongoose点击查看免费下载本文围绕 Mongoose 嵌入式网络库自带的 file-transfer 示例展开完整讲解一个最小 HTTP 客户端 HTTP 服务器组合客户端以单个 POST 请求上传文件并主动将流量切分为小数据块发送服务器则在收到请求后手动接管数据处理、边收边写盘从而避免将整个可能很大的文件缓存在内存中。读完本文你将掌握 Mongoose 事件驱动的 HTTP 请求接管机制MG_EV_HTTP_HDRS、Basic Auth 认证的收发实现、流式分块上传的编程模型以及一套可直接复制运行、支持命令行配置的完整上传方案。示例概览一个数据边到边写的上传链路示例代码位于 tutorials/http/file-transfer共包含四个关键文件server.cHTTP 服务器。监听全部网卡的 8090 端口将web_root目录作为静态资源根目录把upload目录作为上传落盘目录。client.cHTTP 客户端。读取本地文件用单个 POST 请求上传到服务器并通过MG_EV_WRITE事件把流量切分为MG_IO_SIZE大小的小块逐批发送。Makefile一条命令同时构建server与client两个可执行文件兼容 POSIX 与 WindowsMinGW环境。mongoose.c/mongoose.h本示例使用的 Mongoose 单文件源码仓库根目录 mongoose.c / mongoose.h 的打包副本。该示例的设计动机非常明确常规的 HTTP 服务器在处理请求时会先把整个请求体缓冲进内存如果用户上传的是一个几百 MB 的文件而设备只有几十 KB RAM这种做法必然失败。因此服务器主动在收到 HTTP 头MG_EV_HTTP_HDRS的时机接管连接把随后到达的 body 数据边收边写到磁盘全程不缓存完整文件。上传操作还要求 Basic Auth 认证默认用户名/密码为user/pass客户端与服务器均可通过命令行参数修改只有通过认证的请求才能写入文件。构建一条 make 命令产出两个可执行文件原文档建议先配置好开发环境然后进入示例目录构建。在当前仓库中的实际操作路径为cd tutorials/http/file-transfer make clean all执行后会在当前目录生成server与client两个可执行文件。其中make all的默认行为是构建后直接启动服务器见 Makefile 中的all: example目标与$(RUN) ./$(SPROG) $(SARGS)因此想手动控制启动顺序时可以分别执行make server make client从 Makefile 可以看到构建细节服务器与客户端分别由server.c mongoose.c与client.c mongoose.c编译公共编译选项为CFLAGS -W -Wall -Wextra -g -I.通过CFLAGS_MONGOOSE变量预留了 Mongoose 构建选项例如-DMG_ENABLE_LINES可按需追加在 Windows 上ifeq ($(OS),Windows_NT)会自动切换到gcc-lws2_32输出server.exe/client.exe删除命令也切换为cmd /C del /Q /F /S说明该示例可在 Windows 下用 MinGW 直接构建make clean会清理两个程序及所有中间产物。运行三步完成一次认证上传1. 启动服务器在一个终端中启动服务器默认监听所有网卡的 8090 端口./server正常启动时输出如下日志6332b7 2 server.c:157:main Mongoose version : v7.12 6332b7 2 server.c:159:main Listening on : http://0.0.0.0:8090 6332b7 2 server.c:160:main Web root : [/home/mongoose/examples/file-transfer/web_root] 6332b7 2 server.c:161:main Uploading to : [/home/mongoose/examples/file-transfer/upload]日志中的路径以你实际构建目录为准。可以看到服务器做了三件事创建事件管理器、以http://0.0.0.0:8090为监听地址调用mg_http_listen()、然后进入mg_mgr_poll()无限事件循环直到收到SIGINT/SIGTERM信号才退出见 server.c 的main()函数。2. 运行客户端上传文件在另一个终端执行客户端默认会把文件以foo.txt为名上传到http://localhost:8090/upload/foo.txt./client -f Makefile ok输出ok即代表上传成功——这是服务器在完成落盘后返回的响应体详见下文服务器状态机。如果想在同一个终端里先启动服务器再开客户端可以先把服务器放到后台运行。3. 用 curl 验证服务器兼容性服务器并不绑定专用客户端它支持任何标准 HTTP 客户端上传例如 curlcurl -su user:pass http://localhost:8090/upload/foo.txt --data-binary Makefile-u user:pass提供 Basic Auth 凭据--data-binary Makefile以二进制原始数据方式上传本机文件请求体不会做任何改写。这条命令足以证明服务器端实现的是标准 HTTP/1.x 协议语义而非自定义私有协议。命令行参数客户端与服务端的完整配置表示例的默认行为是使用硬编码的用户名和密码直接不带参数运行./server或./client会打印 usage 帮助信息。以下为两个程序支持的全部参数。服务器参数server.c参数含义默认值-u NAME允许上传的用户名user-p PWD密码pass-d DIR静态文件服务目录web_root-D DIR上传文件存储目录upload-s SIZE允许的最大文件大小字节10000-l ADDR监听地址http://0.0.0.0:8090-v LEVEL调试级别取值 04MG_LL_INFO例如把最大上传大小放宽到 1 MB、监听 9000 端口./server -s 1000000 -l http://0.0.0.0:9000服务器启动时还会把-d/-D指定的相对目录通过realpath()转换为绝对路径以消除包含..的相对路径带来的歧义server.cmain()中的处理逻辑。客户端参数client.c参数含义默认值-f NAME要发送的本地文件必填缺省直接打印 usage 退出无-u NAME用户名user-p PWD密码pass-U URL完整的服务器 URL包含目标文件名http://localhost:8090/upload/foo.txt-v LEVEL调试级别取值 04MG_LL_INFO典型用法修改目标文件名与凭据后上传./client -f firmware.bin -U http://192.168.1.10:8090/upload/fw.bin -u admin -p secret注意客户端会先调用mg_fs_posix.st()获取文件大小文件不存在或大小为 0 时会直接报open failed退出见 client.cmain()。服务器端实现在 MG_EV_HTTP_HDRS 时机接管连接服务器核心逻辑集中在handle_uploads()回调中其巧妙之处在于在 HTTP body 完整到达之前就接管连接。拦截 /upload 请求并关闭默认 HTTP 处理器if (ev MG_EV_HTTP_HDRS) { struct mg_http_message *hm (struct mg_http_message *) ev_data; if (mg_match(hm-uri, mg_str(/upload/#), NULL)) { c-pfn NULL; // Silence HTTP protocol handler, well take over ...MG_EV_HTTP_HDRS事件表示已收到完整 HTTP 头但 body 未必完整事件定义见 src/event.h。mg_match()用通配模式/upload/#匹配 URI 前缀一旦命中就通过c-pfn NULL静默掉 mongoose 内置的 HTTP 协议处理器把连接的字节流控制权完全交给自己的回调。从底层看这正是 Mongoose HTTP 协议层的既有机制在 src/http.c 的http_cb()中解析出头部后会调用mg_call(c, MG_EV_HTTP_HDRS, hm)通知用户如果用户修改了c-recv中的数据c-recv.len ! old_lenmongoose 会主动洗手——将c-pfn置空并脱离该连接不再尝试按 HTTP 语义继续解析。示例正是利用这一点把后续到达的原始 body 字节直接交给自己的代码处理。认证、限长与路径校验一条完整的安全检查链接管之后服务器依次执行四道检查任一失败都会回复错误并标记c-is_draining 1告诉 mongoose 响应发送完毕后关闭连接认证调用authuser(hm)内部通过mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass))解析Authorization头并与命令行配置的s_user/s_pass比对。失败则返回403 Denied。限长hm-body.len s_max_size时返回400 Too long默认上限 10000 字节。文件名URI 恰好为/upload/hm-uri.len 8时返回400 Name required拼接出的完整路径超过MG_PATH_MAX时返回400 Path is too long。路径安全用snprintf拼接出upload/文件名后调用mg_path_is_sane()校验防止..等路径穿越攻击不合法则返回400 Invalid path。mg_path_is_sane()的实现位于 src/util.c是 mongoose 在文件服务、OTA、dash 等模块中通用的路径安全防线src/http.c、src/ota.c 等均有调用。全部通过后先fs-rm()删除可能存在的同名旧文件再以MG_FS_WRITE打开目标文件把文件句柄存入struct upload_state的fp字段记录expected hm-body.lenContent-Length 给出的总字节数最后调用mg_iobuf_del(c-recv, 0, hm-head.len)把已消费的 HTTP 头从接收缓冲中删掉——这正是上一节所述修改 recv 数据触发 mongoose 脱管的实际动作。边收边写由 MG_EV_READ 驱动的流式落盘状态机if (us-expected 0 c-recv.len 0) { us-received c-recv.len; if (us-fp) fs-wr(us-fp, c-recv.buf, c-recv.len); // Write to file c-recv.len 0; // Delete received data if (us-received us-expected) { mg_http_reply(c, 200, NULL, %lu ok\n, us-received); if (us-fp) fs-cl(us-fp); // Close file memset(us, 0, sizeof(*us)); // Cleanup upload state c-is_draining 1; // Close after response sent } }这段代码对MG_EV_READ新数据到达和MG_EV_HTTP_HDRS头之后紧跟的 body 数据都会执行只要recv缓冲里有数据就把整块数据直接写入文件然后清零recv.len继续等待下一块。当累计received达到expected时回复200%lu ok\n这就是客户端打印的ok关闭文件、清空上传状态并排空连接。关键收益在于无论文件多大接收缓冲中同一时刻只有一小块数据内存占用与文件大小无关只取决于单次到达的数据量。struct upload_state本身被存放在连接的c-data指针所指区域随连接生命周期自动管理定义见 server.c。非上传请求仍走标准静态服务} else if (ev MG_EV_HTTP_MSG c-pfn ! NULL) { struct mg_http_message *hm (struct mg_http_message *) ev_data; struct mg_http_serve_opts opts {0}; opts.root_dir s_root_dir; mg_http_serve_dir(c, hm, opts); }cb()回调中对普通请求c-pfn未被置空即未被接管使用mg_http_serve_dir()提供web_root目录的静态文件服务。因此同一端口上上传接口 静态页面两者共存浏览器访问/能看到web_root下的页面向/upload/xxx发 POST 则走流式写入路径。客户端实现连接、认证与分块发送MG_EV_CONNECT 时机组装请求客户端在MG_EV_CONNECT已与服务器建立 TCP 连接事件中用mg_printf()直接拼装 HTTP 请求行与头mg_printf(c, POST %s HTTP/1.0\r\n Host: %.*s\r\n Content-Type: octet-stream\r\n Content-Length: %d\r\n, mg_url_uri(s_url), (int) host.len, host.buf, fsize); mg_http_bauth(c, s_user, s_pass); // Add Basic auth header mg_printf(c, %s, \r\n); // End HTTP headersmg_url_uri()/mg_url_host()从-U指定的 URL 中拆出路径与主机名Content-Length填的是本地文件大小fsize之后调用mg_http_bauth()追加认证头最后输出空行结束头部。mg_http_bauth()的实现在 src/http.c它直接把Authorization: Basic前缀、user、:、pass拼接后做 Base64 编码写入发送缓冲并补上\r\n。也就是说Authorization: Basic base64(user:pass)是客户端与服务端之间的认证契约。MG_EV_WRITE 时机按 MG_IO_SIZE 分块发送} else if (ev MG_EV_WRITE c-send.len MG_IO_SIZE) { uint8_t *buf alloca(MG_IO_SIZE); size_t len MG_IO_SIZE - c-send.len; len fsize len ? fsize : len; fd-fs-rd(fd-fd, buf, len); mg_send(c, buf, len); fsize - len;MG_EV_WRITE表示发送缓冲被部分清空、可以继续写入数据。此时从本地文件读取最多MG_IO_SIZE字节减去仍在发送缓冲中的残余量用mg_send()送出如此反复直到文件读完。MG_IO_SIZE是 mongoose 的 IO 缓冲增长粒度在 src/config.h 中默认定义为 512 字节而 POSIX 平台src/arch_unix.h覆盖为 16384因此桌面环境下每次实际发送约 16 KB。这正是文档所述shaping traffic to send small data chunks——把一个大文件拆成一批小数据块按序发送避免一次性把整个文件塞进发送缓冲。收到响应后结束事件循环在MG_EV_HTTP_MSG中打印响应体即服务器返回的ok随后关闭文件、设置done标志让主循环退出MG_EV_ERROR分支则负责连接失败/超时时的清理。连接超时由MG_EV_POLL中记录的mg_millis() s_timeout_ms默认 1500 ms判断超时会通过mg_error()断开。客户端主流程非常精简client.cmain()mg_mgr_init()→mg_http_connect(mgr, s_url, fn, done)→ 循环mg_mgr_poll(mgr, 50)直到done为真 →mg_mgr_free()全程事件驱动无阻塞等待。关键 API 与机制小结API / 机制作用出处MG_EV_HTTP_HDRS收到完整 HTTP 头但 body 未齐时触发是接管请求的最佳时机src/event.h、src/http.cc-pfn NULL静默内置 HTTP 协议处理器由用户代码接管原始字节流server.cmg_http_creds()服务端解析 Authorization支持 Basic / Bearer / Cookie / query 多种来源src/http.cmg_http_bauth()客户端生成Authorization: Basic base64(user:pass)头src/http.cmg_path_is_sane()校验文件路径防御..路径穿越src/util.cmg_match(uri, /upload/#)通配匹配上传 URIserver.cmg_http_reply()发送带状态码的 HTTP 响应src/http.cmg_http_serve_dir()静态目录服务非上传请求server.c值得强调的是mg_http_creds()的通用性从源码src/http.c可以看到它依次尝试Authorization: Basic、Authorization: Bearer、Cookie 中的access_token以及 query 参数中的access_token因此同一套服务器代码稍作修改即可扩展为 Bearer Token 或 cookie 会话认证而不必改动协议处理框架。安全边界与适用限制最后梳理本示例体现的工程约束这些也是把示例移植到产品时应当保留的底线认证是上传的门禁未通过authuser()的请求一律403 Denied且连接随即关闭。生产环境应至少将凭据改为强口令并建议切换到 HTTPSmongoose 支持 TLS参见仓库根目录 tls.h 及相关驱动。大小限制-s参数默认 10000 字节在接收前就依据 Content-Length 拦截超限请求避免磁盘被撑爆但由于检查发生在头部阶段若客户端使用Transfer-Encoding: chunked且不声明长度则需要额外的累计计数逻辑。路径安全mg_path_is_sane()与MG_PATH_MAX双重把关杜绝路径穿越与超长路径。内存恒定流式写盘确保内存占用与文件大小解耦这是本示例在低 RAM 嵌入式设备上可行的根本原因对应地客户端也通过MG_IO_SIZE分块发送避免大文件整体进缓冲。如果需要在此基础上继续扩展可以参考仓库中 tutorials/http/file-upload-html-form、tutorials/http/file-upload-multiple-posts 与 tutorials/http/file-upload-single-post 等示例它们展示了 HTML 表单、多 POST 分片上传等不同形态的落盘方案Mongoose 的 HTTP 协议核心实现与所有相关事件定义则可直接阅读 src/http.c 与 src/http.h。赞分享嵌入式网络通信物联网【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址https://gitcode.com/gh_mirrors/mon/mongoose点击查看免费下载相关推荐NocoBase 文件上传 HTTP API 实战服务端上传与 S3-Pro 客户端直传NocoBase 文件上传 HTTP API 实战服务端上传与 S3 Pro 客户端直传 NocoBase 的文件管理器 nocobase/plugin低代码后端前端人工智能AI 应用工作流自动化Neko 如何启用文件传输插件在客户端与服务器之间传文件Neko 如何启用文件传输插件在客户端与服务器之间传文件 Neko V3 把 V2 时代的 GET /file 文件传输功能整体移到了内置的 File Tra后端音视频NocoBase 文件管理器 HTTP API 完整指南服务端上传与客户端直传实战NocoBase 文件管理器 HTTP API 完整指南服务端上传与客户端直传实战 导读 NocoBase 的附件字段Attachment与文件表Fil低代码后端前端人工智能AI 应用工作流自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考