随着物联网技术的快速发展,高性能、低功耗、多协议的无线通信芯片成为智能设备的核心组件。海思 WS63 芯片(Hi3863V100)作为一款集成了 Wi-Fi 6、星闪 SLE 1.0 和 BLE 5.2 三模通信协议的物联网 SoC 芯片,凭借其出色的性能和丰富的接口资源,为开发者提供了强大的硬件平台。
一、项目背景
Mongoose是一个功能强大的嵌入式网络库,支持TCP、UDP、HTTP、WebSocket、MQTT等多种协议。在海思WS63嵌入式平台上,通过移植Mongoose网络库,可以快速实现网络通信功能,包括HTTP客户端/服务器、WebSocket通信、文件下载等。
本文档详细介绍如何将Mongoose网络库移植到海思WS63平台,并介绍其使用方法、注意事项以及如何发送GET/POST请求和下载文件。
在线文档地址 :https://docs.hisilicon.com/repos/fbb_ws63/zh-CN/master/
论坛地址 :https://developers.hisilicon.com/forum/0133146886267870001
SDK代码仓地址 :https://gitcode.com/HiSpark/fbb_ws63
二、Mongoose简介
Mongoose是一个用纯C语言编写的网络库,其设计理念体现了"简单即美"的哲学。整个库的核心文件只有两个:mongoose.c和mongoose.h,这种极简的设计使得集成变得异常简单。开发者只需要将这两个文件复制到项目中,就能立即开始网络编程。这种设计思路与传统的网络库形成了鲜明对比,后者往往需要复杂的配置和依赖管理。
2.1 Mongoose特性
Mongoose是一个事件驱动的非阻塞网络库,具有以下特点:
- 跨平台支持:支持Linux/UNIX、MacOS、Windows、Android以及多种嵌入式平台
- 内置协议:支持TCP/UDP、HTTP、WebSocket、MQTT、DNS等协议
- 轻量级:代码体积小,运行时内存占用低
- 易于集成:只需复制mongoose.c和mongoose.h两个文件即可集成
- 灵活的TCP/IP栈支持:可运行在lwIP、Zephyr、Azure等TCP/IP栈之上,也可使用内置TCP/IP栈
- 内置TLS 1.3:支持内置TLS栈,也可使用mbedTLS、OpenSSL等外部TLS库
- 异步DNS解析:内置异步DNS解析器
mongoose的github仓 : https://github.com/cesanta/mongoose
核心特性分析
Mongoose的核心特性可以从以下几个方面来理解:
-
跨平台兼容性
Mongoose支持Windows、Linux、macOS、Android等主流操作系统,甚至在嵌入式系统如STM32、ESP32等MCU上也能良好运行。这种广泛的平台支持得益于其底层的抽象设计,将平台相关的网络操作封装在统一的API之下。在实际开发中,这意味着开发者可以编写一套代码,然后在不同的平台上编译运行,大大提高了开发效率和代码复用性。
-
协议支持丰富
库内置了对HTTP、HTTPS、WebSocket、MQTT、TCP、UDP等多种协议的支持。这种全面的协议覆盖使得开发者可以用同一套代码处理不同的网络需求,大大提高了开发效率。特别是在物联网应用中,设备可能需要同时支持HTTP API接口、WebSocket实时通信和MQTT消息队列,Mongoose的协议支持让这些需求变得简单易实现。
-
事件驱动架构
Mongoose采用事件驱动的编程模型,通过回调函数处理各种网络事件。这种设计模式特别适合处理高并发的网络应用,避免了传统多线程编程中的锁竞争问题。事件驱动架构的核心思想是:当网络事件发生时,系统会调用相应的回调函数来处理事件,而不是通过轮询或阻塞等待的方式。这种设计使得系统能够高效地处理大量并发连接。
-
轻量级设计
整个库的代码量控制在合理范围内,内存占用小,启动速度快。这使得Mongoose特别适合资源受限的嵌入式环境。在实际测试中,Mongoose的内存占用通常在几十KB到几百KB之间,这对于嵌入式设备来说是非常理想的。同时,库的启动时间也很短,通常在毫秒级别,这对于需要快速响应的应用场景非常重要。

2.2 适用场景
- HTTP客户端/服务器
- WebSocket通信
- MQTT客户端
- RESTful API服务
- 文件上传/下载
- 固件升级(OTA)
- 设备远程监控
三、移植到海思WS63平台
3.1 获取Mongoose源码
Mongoose源码可以从GitHub官方仓库获取:
bash
git clone https://github.com/cesanta/mongoose.git
或者直接下载最新版本的源码包。本项目使用的版本是基于tag_7.20分支。
3.2 源码集成
将Mongoose源码集成到项目中,主要包含以下文件:
xiaohong/src/protocols/mongoose/
├── mongoose.c # Mongoose核心源码
├── mongoose.h # Mongoose头文件
├── mongoose_config.h # 平台配置文件
├── mongoose_embed_defaults.h # 默认配置
├── mongoose_port_random.c # 随机数生成移植
├── LICENSE # 许可证
└── README.md # 说明文档
3.3 平台配置(mongoose_config.h)
配置文件是移植的核心,需要根据海思WS63平台特性进行配置:
c
#pragma once
/* 基础头文件包含 */
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
/* 日志配置 */
#define MG_ENABLE_LOG 1
#define MG_ENABLE_CUSTOM_LOG 1
#define MG_ENABLE_CUSTOM_MILLIS 1
/* 随机数生成:使用自定义实现,禁止回落到rand() */
#define MG_ENABLE_CUSTOM_RANDOM 1
/* Socket可写配置 */
#define MG_SOCK_WRITABLE_AFTER_CONNECT 1
/* TCP/IP栈配置 */
#define MG_ENABLE_SOCKET 1 // 使用外部TCP/IP栈(lwIP)
#define MG_ENABLE_TCPIP 0 // 禁用Mongoose内置TCP/IP栈
/* lwIP配置 */
#define MG_ENABLE_LWIP 1
#if MG_ENABLE_LWIP
#include "lwip/pbuf.h"
#include "lwip/dns.h"
#include "lwip/ip_addr.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include "lwip/err.h"
/* LiteOS musl的netinet/in.h在_GNU_SOURCE/_BSD_SOURCE下已有struct ip_mreq */
#if !defined(_NETINET_IN_H) || (!defined(_GNU_SOURCE) && !defined(_BSD_SOURCE))
typedef struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
} ip_mreq;
#endif
/* LiteOS/lwIP:套接字fd应走lwip_close */
#ifndef closesocket
#define closesocket(x) lwip_close(x)
#endif
#endif
/* 打包文件系统配置 */
#define MG_ENABLE_PACKED_FS 1
/* Readline功能(禁用) */
#define MG_ENABLE_RL 0
/* I/O多路复用配置 */
#define MG_ENABLE_POLL 0
#define MG_ENABLE_EPOLL 0
/* OHOS RISC-V musl工具链不提供alloca符号,使用GCC内置函数 */
#if defined(__GNUC__) && !defined(alloca)
#define alloca __builtin_alloca
#endif
/* I/O缓冲区大小配置 */
#ifndef MG_IO_SIZE
#define MG_IO_SIZE 4096
#endif
/* 文件系统配置 */
#define MG_ENABLE_POSIX_FS 0
#define MG_PATH_MAX 100
#define MG_ENABLE_DIRLIST 0
3.4 平台适配层实现
3.4.1 日志输出适配
c
/* 在mongoose_config.h中定义自定义日志函数 */
#define MG_ENABLE_CUSTOM_LOG 1
/* 在应用代码中实现日志函数 */
void mg_log_custom(const char *fmt, va_list ap) {
elog_v("mongoose", fmt, ap);
}
3.4.2 时间获取适配
c
/* 在mongoose_config.h中定义自定义时间函数 */
#define MG_ENABLE_CUSTOM_MILLIS 1
/* 在应用代码中实现时间函数 */
uint64_t mg_millis_custom(void) {
return osKernelGetTickCount();
}
3.4.3 随机数生成适配
c
/* mongoose_port_random.c - 线程安全的随机数生成 */
#include "mongoose.h"
#include <stdlib.h>
#include <time.h>
static osMutexId_t random_mutex = NULL;
uint32_t mg_random_custom(void) {
if (random_mutex == NULL) {
random_mutex = osMutexNew(NULL);
}
uint32_t result;
osMutexAcquire(random_mutex, osWaitForever);
result = (uint32_t)rand();
osMutexRelease(random_mutex);
return result;
}
3.5 编译配置
在CMakeLists.txt或BUILD.gn中添加Mongoose源码:
cmake
# CMakeLists.txt
add_library(mongoose STATIC
protocols/mongoose/mongoose.c
protocols/mongoose/mongoose_port_random.c
)
target_include_directories(mongoose PUBLIC
protocols/mongoose
)
gn
# BUILD.gn
source_set("mongoose") {
sources = [
"protocols/mongoose/mongoose.c",
"protocols/mongoose/mongoose_port_random.c",
]
include_dirs = [
"protocols/mongoose",
]
}
四、Mongoose使用方法
4.1 基本使用流程
Mongoose的基本使用流程如下:
- 初始化事件管理器 :
mg_mgr_init() - 创建连接 :
mg_http_connect()、mg_ws_connect()等 - 设置事件处理器:定义回调函数处理各种事件
- 运行事件循环 :
mg_mgr_poll() - 清理资源 :
mg_mgr_free()
4.2 线程安全注意事项
重要:Mongoose不是线程安全的,所有Mongoose API调用必须在事件循环线程中执行。如果需要在其他线程中操作连接,必须使用互斥锁保护:
c
/* 全局互斥锁 */
static osMutexId_t g_mg_api_mutex = NULL;
void mongoose_api_lock(void)
{
if (g_mg_api_mutex == NULL) {
g_mg_api_mutex = osMutexNew(NULL);
}
if (g_mg_api_mutex != NULL) {
osMutexAcquire(g_mg_api_mutex, osWaitForever);
}
}
void mongoose_api_unlock(void)
{
if (g_mg_api_mutex != NULL) {
osMutexRelease(g_mg_api_mutex);
}
}
/* 在所有Mongoose API调用前后加锁 */
mongoose_api_lock();
mg_http_connect(&mgr, url, handler, data);
mongoose_api_unlock();
4.3 事件循环线程
创建专用的事件循环线程:
c
static void mg_poll_thread(void *arg)
{
struct mg_mgr *mgr = (struct mg_mgr *)arg;
while (true) {
mongoose_api_lock();
mg_mgr_poll(mgr, 100);
mongoose_api_unlock();
/* 避免CPU空转 */
osal_msleep(1U);
}
}
/* 创建事件循环线程 */
osThreadAttr_t attr = {0};
attr.name = "mg_poll";
attr.stack_size = 1024 * 12;
attr.priority = 24;
osThreadNew(mg_poll_thread, &mgr, &attr);
五、HTTP客户端使用
5.1 发送GET请求
c
#include "mongoose.h"
#include "elog.h"
#define HTTP_GET_URL "http://183.93.148.7:8002/xiaozhi/ota/"
#define CONNECT_TIMEOUT_MS 5000
static bool http_get_done = false;
static void http_get_handler(struct mg_connection *c, int ev, void *ev_data)
{
if (ev == MG_EV_OPEN) {
/* 连接打开,设置超时 */
*(uint64_t *)c->data = mg_millis() + CONNECT_TIMEOUT_MS;
}
else if (ev == MG_EV_POLL) {
/* 检查连接超时 */
if (mg_millis() > *(uint64_t *)c->data &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Connect timeout");
}
}
else if (ev == MG_EV_CONNECT) {
/* 连接建立成功,发送GET请求 */
struct mg_str host = mg_url_host(HTTP_GET_URL);
mg_printf(c,
"GET %s HTTP/1.1\r\n"
"Host: %.*s\r\n"
"Connection: close\r\n"
"Accept: */*\r\n"
"\r\n",
mg_url_uri(HTTP_GET_URL),
(int)host.len, host.buf);
elog_i("http", "GET request sent");
}
else if (ev == MG_EV_HTTP_MSG) {
/* 收到HTTP响应 */
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
int status = mg_http_status(hm);
elog_i("http", "HTTP status: %d", status);
elog_i("http", "Response body: %.*s",
(int)hm->body.len, hm->body.buf);
c->is_draining = 1;
http_get_done = true;
}
else if (ev == MG_EV_ERROR) {
/* 发生错误 */
elog_e("http", "Error: %s", (char *)ev_data);
http_get_done = true;
}
}
void http_get_example(void)
{
struct mg_mgr mgr;
http_get_done = false;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_INFO);
elog_i("http", "Connecting to %s", HTTP_GET_URL);
if (!mg_http_connect(&mgr, HTTP_GET_URL, http_get_handler, &http_get_done)) {
elog_e("http", "mg_http_connect failed");
mg_mgr_free(&mgr);
return;
}
/* 事件循环 */
while (!http_get_done) {
mg_mgr_poll(&mgr, 50);
}
mg_mgr_free(&mgr);
elog_i("http", "HTTP GET completed");
}
5.2 发送POST请求
c
#include "mongoose.h"
#include "elog.h"
#define HTTP_POST_URL "http://example.com/api/data"
#define CONNECT_TIMEOUT_MS 5000
static bool http_post_done = false;
static void http_post_handler(struct mg_connection *c, int ev, void *ev_data)
{
if (ev == MG_EV_OPEN) {
*(uint64_t *)c->data = mg_millis() + CONNECT_TIMEOUT_MS;
}
else if (ev == MG_EV_POLL) {
if (mg_millis() > *(uint64_t *)c->data &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Connect timeout");
}
}
else if (ev == MG_EV_CONNECT) {
/* 连接建立成功,发送POST请求 */
struct mg_str host = mg_url_host(HTTP_POST_URL);
const char *json_data = "{\"name\":\"test\",\"value\":123}";
mg_printf(c,
"POST %s HTTP/1.1\r\n"
"Host: %.*s\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
mg_url_uri(HTTP_POST_URL),
(int)host.len, host.buf,
(int)strlen(json_data),
json_data);
elog_i("http", "POST request sent: %s", json_data);
}
else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
int status = mg_http_status(hm);
elog_i("http", "HTTP status: %d", status);
elog_i("http", "Response body: %.*s",
(int)hm->body.len, hm->body.buf);
c->is_draining = 1;
http_post_done = true;
}
else if (ev == MG_EV_ERROR) {
elog_e("http", "Error: %s", (char *)ev_data);
http_post_done = true;
}
}
void http_post_example(void)
{
struct mg_mgr mgr;
http_post_done = false;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_INFO);
elog_i("http", "Connecting to %s", HTTP_POST_URL);
if (!mg_http_connect(&mgr, HTTP_POST_URL, http_post_handler, &http_post_done)) {
elog_e("http", "mg_http_connect failed");
mg_mgr_free(&mgr);
return;
}
while (!http_post_done) {
mg_mgr_poll(&mgr, 50);
}
mg_mgr_free(&mgr);
elog_i("http", "HTTP POST completed");
}
5.3 使用高级API简化HTTP请求
Mongoose提供了高级API可以简化HTTP请求:
c
/* 使用mg_http_fetch发送GET请求 */
void http_get_simple_example(void)
{
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_fetch(&mgr, "http://example.com/api/data",
&(struct mg_http_fetch_opts){
.method = "GET",
.headers = "Accept: application/json\r\n",
.body = "",
.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;
elog_i("http", "Response: %.*s",
(int)hm->body.len, hm->body.buf);
c->is_draining = 1;
}
},
.fn_data = NULL
});
while (true) {
mg_mgr_poll(&mgr, 100);
}
}
/* 使用mg_http_fetch发送POST请求 */
void http_post_simple_example(void)
{
struct mg_mgr mgr;
mg_mgr_init(&mgr);
const char *json_data = "{\"name\":\"test\",\"value\":123}";
mg_http_fetch(&mgr, "http://example.com/api/data",
&(struct mg_http_fetch_opts){
.method = "POST",
.headers = "Content-Type: application/json\r\n",
.body = json_data,
.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;
elog_i("http", "Response: %.*s",
(int)hm->body.len, hm->body.buf);
c->is_draining = 1;
}
},
.fn_data = NULL
});
while (true) {
mg_mgr_poll(&mgr, 100);
}
}
六、文件下载
6.1 流式下载到文件系统
以下是一个完整的文件下载示例,支持流式下载(边收边写)到LittleFS文件系统:
c
#include "mongoose.h"
#include "littlefs_adapt.h"
#include "elog.h"
#include "cmsis_os2.h"
#define DOWNLOAD_URL "http://example.com/file.bin"
#define DOWNLOAD_PATH "/system/file.bin"
#define CONNECT_TIMEOUT_MS 30000
#define BODY_IDLE_TIMEOUT_MS 30000
#define OVERALL_TIMEOUT_MS 300000
typedef enum {
BODY_MODE_NONE = 0,
BODY_MODE_CONTENT_LENGTH,
BODY_MODE_CHUNKED,
BODY_MODE_UNTIL_CLOSE,
} body_mode_t;
typedef struct {
int fd;
int result;
volatile bool done;
body_mode_t mode;
size_t content_remaining;
size_t chunk_payload_left;
bool headers_ok;
uint64_t last_body_ms;
uint64_t connect_deadline_ms;
char url_buf[256];
char path[96];
} download_ctx_t;
static void download_abort(struct mg_connection *c, download_ctx_t *ctx, int err)
{
if (ctx->done) return;
if (ctx->fd >= 0) {
fs_adapt_close(ctx->fd);
ctx->fd = -1;
fs_adapt_delete(ctx->path);
}
ctx->result = err;
ctx->done = true;
c->is_closing = 1;
}
static void download_finish_ok(struct mg_connection *c, download_ctx_t *ctx)
{
if (ctx->done) return;
if (ctx->fd >= 0) {
fs_adapt_sync(ctx->fd);
fs_adapt_close(ctx->fd);
ctx->fd = -1;
}
ctx->result = 0;
ctx->done = true;
c->is_closing = 1;
}
static void strip_http_headers(struct mg_connection *c, struct mg_http_message *hm)
{
size_t hdr_len = (size_t)(hm->body.buf - (char *)c->recv.buf);
if (hdr_len > c->recv.len) return;
mg_iobuf_del(&c->recv, 0, hdr_len);
}
static int parse_content_length(const struct mg_str *s, size_t *out)
{
char tmp[32];
if (s == NULL || s->buf == NULL) return -1;
size_t n = s->len < sizeof(tmp) - 1 ? s->len : sizeof(tmp) - 1;
memcpy(tmp, s->buf, n);
tmp[n] = '\0';
char *endp = NULL;
unsigned long v = strtoul(tmp, &endp, 10);
if (endp == tmp) return -1;
*out = (size_t)v;
return 0;
}
static void consume_content_length(struct mg_connection *c, download_ctx_t *ctx)
{
while (ctx->content_remaining > 0 && c->recv.len > 0) {
size_t n = c->recv.len;
if (n > ctx->content_remaining) {
n = ctx->content_remaining;
}
int w = fs_adapt_write(ctx->fd, (const char *)c->recv.buf, (unsigned int)n);
if (w < 0 || (size_t)w != n) {
download_abort(c, ctx, -1);
return;
}
mg_iobuf_del(&c->recv, 0, n);
ctx->content_remaining -= n;
ctx->last_body_ms = mg_millis();
}
if (ctx->content_remaining == 0) {
download_finish_ok(c, ctx);
}
}
static void consume_chunked(struct mg_connection *c, download_ctx_t *ctx)
{
for (;;) {
if (c->recv.len == 0) return;
if (ctx->chunk_payload_left == 0) {
/* 解析chunk大小 */
char *buf = (char *)c->recv.buf;
size_t i = 0;
while (i + 1 < c->recv.len && !(buf[i] == '\r' && buf[i + 1] == '\n')) {
i++;
}
if (i + 1 >= c->recv.len) return;
char tmp[12];
if (i >= sizeof(tmp)) {
download_abort(c, ctx, -1);
return;
}
memcpy(tmp, buf, i);
tmp[i] = '\0';
char *endp = NULL;
unsigned long csz = strtoul(tmp, &endp, 16);
if (endp == tmp || csz == ULONG_MAX) {
download_abort(c, ctx, -1);
return;
}
mg_iobuf_del(&c->recv, 0, i + 2);
if (csz == 0) {
download_finish_ok(c, ctx);
return;
}
ctx->chunk_payload_left = (size_t)csz;
ctx->last_body_ms = mg_millis();
continue;
}
/* 写入chunk数据 */
size_t n = c->recv.len;
if (n > ctx->chunk_payload_left) {
n = ctx->chunk_payload_left;
}
int w = fs_adapt_write(ctx->fd, (const char *)c->recv.buf, (unsigned int)n);
if (w < 0 || (size_t)w != n) {
download_abort(c, ctx, -1);
return;
}
mg_iobuf_del(&c->recv, 0, n);
ctx->chunk_payload_left -= n;
ctx->last_body_ms = mg_millis();
if (ctx->chunk_payload_left == 0) {
/* 跳过chunk结束标记\r\n */
if (c->recv.len < 2) return;
if (((char *)c->recv.buf)[0] != '\r' || ((char *)c->recv.buf)[1] != '\n') {
download_abort(c, ctx, -1);
return;
}
mg_iobuf_del(&c->recv, 0, 2);
}
}
}
static void download_handler(struct mg_connection *c, int ev, void *ev_data)
{
download_ctx_t *ctx = (download_ctx_t *)c->fn_data;
if (ctx == NULL) return;
if (ev == MG_EV_OPEN) {
ctx->connect_deadline_ms = mg_millis() + CONNECT_TIMEOUT_MS;
}
else if (ev == MG_EV_POLL) {
if (!ctx->headers_ok) {
if (mg_millis() > ctx->connect_deadline_ms &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Download connect timeout");
}
} else if (!ctx->done) {
if ((mg_millis() - ctx->last_body_ms) > BODY_IDLE_TIMEOUT_MS) {
mg_error(c, "Download body idle timeout");
}
}
}
else if (ev == MG_EV_CONNECT) {
/* 发送GET请求 */
struct mg_str host = mg_url_host(ctx->url_buf);
unsigned short port = mg_url_port(ctx->url_buf);
if (port == 80) {
mg_printf(c,
"GET %s HTTP/1.1\r\n"
"Host: %.*s\r\n"
"Connection: close\r\n"
"Accept: */*\r\n"
"\r\n",
mg_url_uri(ctx->url_buf),
(int)host.len, host.buf);
} else {
mg_printf(c,
"GET %s HTTP/1.1\r\n"
"Host: %.*s:%u\r\n"
"Connection: close\r\n"
"Accept: */*\r\n"
"\r\n",
mg_url_uri(ctx->url_buf),
(int)host.len, host.buf,
(unsigned)port);
}
}
else if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
int status = mg_http_status(hm);
if (status != 200) {
download_abort(c, ctx, -1);
return;
}
/* 确定传输模式 */
struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
bool chunked = (te != NULL && mg_strcasecmp(*te, mg_str("chunked")) == 0);
struct mg_str *clh = mg_http_get_header(hm, "Content-Length");
if (chunked) {
ctx->mode = BODY_MODE_CHUNKED;
} else if (clh != NULL) {
size_t cl = 0;
if (parse_content_length(clh, &cl) != 0) {
download_abort(c, ctx, -1);
return;
}
ctx->mode = BODY_MODE_CONTENT_LENGTH;
ctx->content_remaining = cl;
} else {
ctx->mode = BODY_MODE_UNTIL_CLOSE;
}
/* 创建目标文件 */
ctx->fd = fs_adapt_open(ctx->path, O_WRONLY | O_CREAT | O_TRUNC);
if (ctx->fd < 0) {
download_abort(c, ctx, -1);
return;
}
strip_http_headers(c, hm);
ctx->headers_ok = true;
ctx->last_body_ms = mg_millis();
/* 开始接收数据 */
if (ctx->mode == BODY_MODE_CONTENT_LENGTH) {
consume_content_length(c, ctx);
} else if (ctx->mode == BODY_MODE_CHUNKED) {
consume_chunked(c, ctx);
}
}
else if (ev == MG_EV_READ) {
if (!ctx->headers_ok || ctx->done) return;
if (ctx->mode == BODY_MODE_CONTENT_LENGTH) {
consume_content_length(c, ctx);
} else if (ctx->mode == BODY_MODE_CHUNKED) {
consume_chunked(c, ctx);
}
}
else if (ev == MG_EV_CLOSE) {
if (ctx->done) return;
if (!ctx->headers_ok) {
ctx->result = -1;
ctx->done = true;
return;
}
if (ctx->mode == BODY_MODE_UNTIL_CLOSE) {
if (ctx->fd >= 0) {
download_finish_ok(c, ctx);
} else {
ctx->result = -1;
ctx->done = true;
}
} else {
download_abort(c, ctx, -1);
}
}
else if (ev == MG_EV_ERROR) {
download_abort(c, ctx, -1);
}
}
int download_file(const char *url, const char *local_path)
{
if (url == NULL || local_path == NULL) return -1;
if (strncmp(url, "http://", 7) != 0) return -1;
struct mg_mgr mgr;
mg_mgr_init(&mgr);
download_ctx_t *ctx = (download_ctx_t *)calloc(1, sizeof(*ctx));
if (ctx == NULL) {
mg_mgr_free(&mgr);
return -1;
}
ctx->fd = -1;
ctx->result = -1;
snprintf(ctx->url_buf, sizeof(ctx->url_buf), "%s", url);
snprintf(ctx->path, sizeof(ctx->path), "%s", local_path);
struct mg_connection *c = mg_http_connect(&mgr, ctx->url_buf, download_handler, ctx);
if (c == NULL) {
free(ctx);
mg_mgr_free(&mgr);
return -1;
}
uint64_t overall_deadline = mg_millis() + OVERALL_TIMEOUT_MS;
while (!ctx->done && mg_millis() < overall_deadline) {
mg_mgr_poll(&mgr, 50);
osal_msleep(1);
}
if (!ctx->done) {
mg_error(c, "Download overall timeout");
mg_mgr_poll(&mgr, 50);
}
int result = ctx->result;
mg_mgr_free(&mgr);
free(ctx);
elog_i("download", "Download %s: %s",
result == 0 ? "success" : "failed", local_path);
return result;
}
/* 使用示例 */
void download_example(void)
{
const char *url = "http://example.com/file.bin";
const char *path = "/system/file.bin";
elog_i("download", "Starting download: %s -> %s", url, path);
int ret = download_file(url, path);
if (ret == 0) {
elog_i("download", "Download completed successfully");
} else {
elog_e("download", "Download failed with error: %d", ret);
}
}
6.2 带进度显示的文件下载
c
typedef struct {
download_ctx_t base;
size_t total_bytes;
size_t downloaded_bytes;
void (*progress_callback)(size_t total, size_t downloaded);
} download_with_progress_ctx_t;
static void download_with_progress_handler(struct mg_connection *c, int ev, void *ev_data)
{
download_with_progress_ctx_t *ctx = (download_with_progress_ctx_t *)c->fn_data;
if (ev == MG_EV_HTTP_HDRS) {
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
struct mg_str *clh = mg_http_get_header(hm, "Content-Length");
if (clh != NULL) {
parse_content_length(clh, &ctx->total_bytes);
}
}
else if (ev == MG_EV_READ) {
/* 更新下载进度 */
if (ctx->base.fd >= 0) {
ctx->downloaded_bytes = fs_adapt_lseek(ctx->base.fd, 0, SEEK_CUR);
if (ctx->progress_callback != NULL) {
ctx->progress_callback(ctx->total_bytes, ctx->downloaded_bytes);
}
}
}
/* 调用基础处理器 */
download_handler(c, ev, ev_data);
}
/* 进度回调函数示例 */
static void download_progress_callback(size_t total, size_t downloaded)
{
if (total > 0) {
int percent = (int)(downloaded * 100 / total);
elog_i("download", "Progress: %d%% (%zu/%zu bytes)",
percent, downloaded, total);
} else {
elog_i("download", "Downloaded: %zu bytes", downloaded);
}
}
七、WebSocket使用
7.1 WebSocket客户端
c
#include "mongoose.h"
#include "elog.h"
#define WS_URL "ws://echo.websocket.org"
static bool ws_connected = false;
static bool ws_done = false;
static void ws_handler(struct mg_connection *c, int ev, void *ev_data)
{
if (ev == MG_EV_WS_OPEN) {
/* WebSocket连接建立 */
elog_i("ws", "WebSocket connected");
ws_connected = true;
/* 发送测试消息 */
mg_ws_send(c, "Hello, WebSocket!", 17, WEBSOCKET_OP_TEXT);
}
else if (ev == MG_EV_WS_MSG) {
/* 收到WebSocket消息 */
struct mg_ws_message *wm = (struct mg_ws_message *)ev_data;
if (wm->flags & WEBSOCKET_OP_TEXT) {
elog_i("ws", "Received text: %.*s",
(int)wm->data.len, wm->data.buf);
} else if (wm->flags & WEBSOCKET_OP_BINARY) {
elog_i("ws", "Received binary: %zu bytes", wm->data.len);
}
}
else if (ev == MG_EV_CLOSE) {
/* WebSocket连接关闭 */
elog_i("ws", "WebSocket closed");
ws_connected = false;
ws_done = true;
}
else if (ev == MG_EV_ERROR) {
elog_e("ws", "WebSocket error: %s", (char *)ev_data);
ws_done = true;
}
}
void websocket_client_example(void)
{
struct mg_mgr mgr;
ws_connected = false;
ws_done = false;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_INFO);
elog_i("ws", "Connecting to %s", WS_URL);
if (!mg_ws_connect(&mgr, WS_URL, ws_handler, NULL, NULL)) {
elog_e("ws", "mg_ws_connect failed");
mg_mgr_free(&mgr);
return;
}
/* 事件循环 */
while (!ws_done) {
mg_mgr_poll(&mgr, 50);
/* 定期发送心跳 */
if (ws_connected && (mg_millis() % 30000) < 50) {
mg_ws_send(c, "ping", 4, WEBSOCKET_OP_PING);
}
}
mg_mgr_free(&mgr);
elog_i("ws", "WebSocket client completed");
}
7.2 带自定义头的WebSocket连接
c
void websocket_with_headers_example(void)
{
struct mg_mgr mgr;
mg_mgr_init(&mgr);
/* 连接时添加自定义HTTP头 */
mg_ws_connect(&mgr, "wss://api.example.com/ws",
ws_handler, NULL,
"Authorization: Bearer token123\r\n"
"Protocol-Version: 1\r\n"
"Device-Id: device001\r\n");
while (true) {
mg_mgr_poll(&mgr, 100);
}
}
八、注意事项和最佳实践
8.1 内存管理
- 合理设置缓冲区大小:
c
/* 根据实际需求设置IO缓冲区大小 */
#define MG_IO_SIZE 4096 /* 默认4KB,可根据需要调整 */
- 及时释放资源:
c
/* 连接关闭后及时释放资源 */
if (ev == MG_EV_CLOSE) {
if (ctx->fd >= 0) {
fs_adapt_close(ctx->fd);
ctx->fd = -1;
}
free(ctx);
}
- 避免内存泄漏:
c
/* 确保所有分配的内存都被释放 */
void cleanup_connection(struct mg_connection *c) {
if (c->fn_data != NULL) {
free(c->fn_data);
c->fn_data = NULL;
}
}
8.2 错误处理
- 超时处理:
c
/* 设置连接超时 */
if (ev == MG_EV_OPEN) {
*(uint64_t *)c->data = mg_millis() + CONNECT_TIMEOUT_MS;
}
/* 检查超时 */
if (ev == MG_EV_POLL) {
if (mg_millis() > *(uint64_t *)c->data &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Connect timeout");
}
}
- 错误恢复:
c
/* 实现自动重连机制 */
static void reconnect_timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *)arg;
if (s_conn == NULL) {
s_conn = mg_http_connect(mgr, url, handler, NULL);
}
}
/* 添加重连定时器 */
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW,
reconnect_timer_fn, &mgr);
8.3 性能优化
- 合理设置poll超时:
c
/* 根据实际需求设置poll超时时间 */
mg_mgr_poll(&mgr, 50); /* 50ms超时,平衡响应性和CPU占用 */
- 避免CPU空转:
c
/* 在poll循环中添加延时 */
while (!done) {
mg_mgr_poll(&mgr, 50);
osal_msleep(1); /* 让出CPU时间片 */
}
- 使用事件驱动模式:
c
/* 充分利用Mongoose的事件驱动特性 */
static void handler(struct mg_connection *c, int ev, void *ev_data) {
switch (ev) {
case MG_EV_CONNECT:
/* 连接建立 */
break;
case MG_EV_READ:
/* 数据可读 */
break;
case MG_EV_WRITE:
/* 数据可写 */
break;
/* 其他事件... */
}
}
8.4 安全考虑
- HTTPS支持:
c
/* 使用HTTPS确保数据安全 */
mg_http_connect(&mgr, "https://example.com/api", handler, NULL);
- 输入验证:
c
/* 验证URL和文件路径 */
if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) {
elog_e("security", "Invalid URL protocol");
return -1;
}
if (strstr(path, "..") != NULL) {
elog_e("security", "Path traversal detected");
return -1;
}
- 资源限制:
c
/* 限制文件大小 */
#define MAX_FILE_SIZE (10 * 1024 * 1024) /* 10MB */
if (content_length > MAX_FILE_SIZE) {
elog_e("security", "File too large");
return -1;
}
8.5 调试技巧
- 启用详细日志:
c
/* 设置日志级别 */
mg_log_set(MG_LL_DEBUG); /* 启用调试日志 */
- 监控连接状态:
c
/* 打印连接状态 */
if (ev == MG_EV_CONNECT) {
elog_i("debug", "Connected: %s", c->url);
} else if (ev == MG_EV_CLOSE) {
elog_i("debug", "Closed: %s", c->url);
}
- 性能分析:
c
/* 记录操作耗时 */
uint64_t start = mg_millis();
/* 执行操作 */
uint64_t elapsed = mg_millis() - start;
elog_i("perf", "Operation took %llu ms", elapsed);
九、常见问题与解决方案
9.1 连接失败
问题:mg_http_connect返回NULL或连接超时。
原因:网络不可达、DNS解析失败、服务器未响应。
解决方案:
c
/* 检查网络连接 */
if (!wifi_is_connected()) {
elog_e("http", "Network not connected");
return -1;
}
/* 使用IP地址代替域名 */
const char *url = "http://192.168.1.100/api/data";
/* 增加超时时间 */
#define CONNECT_TIMEOUT_MS 10000 /* 10秒 */
9.2 内存不足
问题:下载大文件时内存不足。
解决方案:
c
/* 使用流式下载,边收边写 */
/* 参考前面的download_file实现 */
/* 减小缓冲区大小 */
#define MG_IO_SIZE 2048 /* 减小到2KB */
9.3 DNS解析失败
问题:使用域名连接时DNS解析失败。
解决方案:
c
/* 检查DNS配置 */
/* 确保lwIP的DNS服务器已配置 */
/* 使用IP地址代替域名 */
const char *url = "http://183.93.148.7:8002/api/data";
/* 增加DNS解析超时 */
#define DNS_TIMEOUT_MS 5000
9.4 WebSocket连接断开
问题:WebSocket连接频繁断开。
解决方案:
c
/* 实现心跳机制 */
static void send_ping(struct mg_connection *c) {
mg_ws_send(c, "ping", 4, WEBSOCKET_OP_PING);
}
/* 在事件循环中定期发送心跳 */
if (ws_connected && (mg_millis() % 30000) < 50) {
send_ping(c);
}
/* 实现自动重连 */
if (ev == MG_EV_CLOSE) {
/* 延迟后重连 */
osDelay(5000);
mg_ws_connect(&mgr, url, handler, NULL, NULL);
}
十、总结
本文档详细介绍了Mongoose网络库在海思WS63平台上的移植和使用方法,包括:
- 移植步骤:源码集成、平台配置、适配层实现
- HTTP客户端:GET/POST请求的实现方法
- 文件下载:流式下载到文件系统的完整实现
- WebSocket:WebSocket客户端的使用方法
- 注意事项:线程安全、内存管理、错误处理等
- 最佳实践:性能优化、安全考虑、调试技巧
通过本次移植,WS63平台获得了强大的网络通信能力,可以方便地实现HTTP客户端、WebSocket通信、文件下载等功能。Mongoose的事件驱动架构和轻量级设计,使其非常适合在资源受限的嵌入式系统中使用。
十一、参考资料
- Mongoose官方文档:https://mongoose.ws/
- Mongoose GitHub仓库:https://github.com/cesanta/mongoose
- Mongoose API文档:https://mongoose.ws/documentation/
- 海思WS63开发指南
- lwIP文档:https://www.nongnu.org/lwip/2_1_x/
- CMSIS-RTOS2 API文档
https://www.cnblogs.com/ylaoda/p/12032992.html
https://devpress.csdn.net/guangzhou/6979b85fa16c6648a985a396.html