54、C语言与MongoDB基础:MongoDB C Driver、mongoc_client_new、mongoc_collection_find_with_opts
说实话,做了这么多年嵌入式开发,我接触最多的数据库一直是 SQLite 这种轻量级选手。但最近几年,物联网项目越来越复杂,数据量也上来了,很多场景下需要把设备数据上传到云端做分析。这时候,MongoDB 就进入了我的视野。
嗯,今天我们就来聊聊怎么在 C 语言里操作 MongoDB。说白了,就是用官方提供的 C Driver 库,把我们的嵌入式设备跟 MongoDB 数据库连起来。
MongoDB C Driver 是什么?
MongoDB C Driver 是 MongoDB 官方提供的 C 语言客户端库。它允许我们用 C 代码连接 MongoDB 服务器,执行增删改查操作。
我个人习惯把它叫做「胶水层」—— 它把 MongoDB 的 wire protocol 封装成了 C 函数,我们只需要调用几个 API 就能完成数据库操作。
核心要点:MongoDB C Driver 不是 MongoDB 自带的,需要单独安装。在嵌入式 Linux 环境下,通常用交叉编译的方式把它集成到项目中。
环境准备:安装 MongoDB C Driver
在开始写代码之前,得先把环境搭好。我一般在 Ubuntu 或 Debian 系统上开发,安装命令如下:
# 安装依赖
sudo apt-get install cmake libssl-dev libsasl2-dev
# 下载源码
wget https://github.com/mongodb/mongo-c-driver/releases/download/1.24.0/mongo-c-driver-1.24.0.tar.gz
tar -xzf mongo-c-driver-1.24.0.tar.gz
cd mongo-c-driver-1.24.0
# 编译安装
mkdir cmake-build
cd cmake-build
cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF ..
make
sudo make install
小提示:我在项目中遇到过一个问题 —— 如果目标板是 ARM 架构,记得用交叉编译工具链。否则编译出来的库在板子上跑不起来。
核心 API 详解
今天重点讲三个 API:mongoc_client_new、mongoc_collection_find_with_opts,以及配套的初始化与清理函数。
1. mongoc_client_new —— 创建客户端连接
这个函数用来创建一个 MongoDB 客户端对象。说白了,就是告诉驱动:「我要连哪台服务器,端口是多少」。
#include <mongoc/mongoc.h>
int main() {
mongoc_client_t *client;
// 初始化驱动(必须调用)
mongoc_init();
// 创建客户端连接
client = mongoc_client_new("mongodb://localhost:27017");
if (!client) {
fprintf(stderr, "创建客户端失败\n");
return -1;
}
// ... 后续操作 ...
// 清理
mongoc_client_destroy(client);
mongoc_cleanup();
return 0;
}
你想想看,这个连接字符串其实挺灵活的。可以指定用户名密码、认证数据库、副本集等参数。比如:
"mongodb://user:pass@192.168.1.100:27017/admin?replicaSet=myReplSet"
注意:我曾经踩过一个坑 —— 忘记调用 mongoc_init() 就直接创建客户端。结果程序跑起来就段错误。嗯,这个初始化函数必须最先调用,而且每个线程只需要调用一次。
2. mongoc_collection_find_with_opts —— 带选项的查询
这个函数是 MongoDB C Driver 里最常用的查询接口。它比老版的 mongoc_collection_find 更灵活,支持传入各种查询选项。
先看一个最简单的例子:
mongoc_collection_t *collection;
mongoc_cursor_t *cursor;
bson_t *filter;
bson_t *opts;
const bson_t *doc;
// 获取集合对象
collection = mongoc_client_get_collection(client, "mydb", "sensors");
// 构造查询条件:查找温度大于30的数据
filter = BCON_NEW("temperature", BCON_INT32(30));
// 构造选项:限制返回10条,按时间降序
opts = BCON_NEW("limit", BCON_INT32(10),
"sort", "{", "timestamp", BCON_INT32(-1), "}");
// 执行查询
cursor = mongoc_collection_find_with_opts(collection, filter, opts, NULL);
// 遍历结果
while (mongoc_cursor_next(cursor, &doc)) {
char *str = bson_as_json(doc, NULL);
printf("%s\n", str);
bson_free(str);
}
// 清理
mongoc_cursor_destroy(cursor);
bson_destroy(filter);
bson_destroy(opts);
mongoc_collection_destroy(collection);
这里有几个关键点:
- filter:查询条件,用 BSON 格式构造。说白了就是 JSON 的二进制版本。
- opts:查询选项,比如 limit、sort、projection 等。
- 第四个参数:可以传一个 read preference,一般传 NULL 用默认值。
BSON 构造技巧
BSON 是 MongoDB 的「母语」。在 C 语言里,我们通常用 BCON_NEW 宏来快速构造 BSON 对象。
我常用的几种构造方式:
| 场景 | 代码示例 |
|---|---|
| 简单等值查询 | BCON_NEW("status", BCON_UTF8("active")) |
| 范围查询 | BCON_NEW("age", "{", "$gte", BCON_INT32(18), "$lte", BCON_INT32(60), "}") |
| 嵌套文档 | BCON_NEW("address", "{", "city", BCON_UTF8("Beijing"), "}") |
| 数组查询 | BCON_NEW("tags", "[", BCON_UTF8("sensor"), BCON_UTF8("iot"), "]") |
经验之谈:我在调试时经常用 bson_as_json() 把 BSON 转成可读的 JSON 字符串打印出来。这样能快速确认构造的查询条件对不对。
完整示例:查询传感器数据
下面是一个完整的实战例子。假设我们有一个物联网项目,传感器每5秒上报一次数据,我们要查询最近1小时内温度超过35度的记录。
#include <stdio.h>
#include <mongoc/mongoc.h>
#include <time.h>
int main() {
mongoc_client_t *client;
mongoc_collection_t *collection;
mongoc_cursor_t *cursor;
bson_t *filter, *opts;
const bson_t *doc;
bson_error_t error;
// 初始化
mongoc_init();
// 连接 MongoDB
client = mongoc_client_new("mongodb://localhost:27017");
if (!client) {
fprintf(stderr, "连接失败\n");
return -1;
}
// 获取集合
collection = mongoc_client_get_collection(client, "iot_db", "sensor_data");
// 构造查询条件
// 条件1:温度 > 35
// 条件2:时间在最近1小时内
time_t now = time(NULL);
time_t one_hour_ago = now - 3600;
filter = BCON_NEW("temperature",
"{", "$gt", BCON_INT32(35), "}",
"timestamp",
"{", "$gte", BCON_INT64(one_hour_ago), "}");
// 选项:按时间降序,只返回20条
opts = BCON_NEW("sort", "{", "timestamp", BCON_INT32(-1), "}",
"limit", BCON_INT32(20));
// 执行查询
cursor = mongoc_collection_find_with_opts(collection, filter, opts, NULL);
// 打印结果
printf("=== 高温传感器数据(最近1小时)===\n");
while (mongoc_cursor_next(cursor, &doc)) {
char *json = bson_as_json(doc, NULL);
printf("%s\n", json);
bson_free(json);
}
// 检查是否有错误
if (mongoc_cursor_error(cursor, &error)) {
fprintf(stderr, "查询出错: %s\n", error.message);
}
// 清理
mongoc_cursor_destroy(cursor);
bson_destroy(filter);
bson_destroy(opts);
mongoc_collection_destroy(collection);
mongoc_client_destroy(client);
mongoc_cleanup();
return 0;
}
知识体系结构图
下面这张图展示了 MongoDB C Driver 的核心调用流程:
避坑指南
我总结了几条实战中容易踩的坑:
- 内存泄漏:BSON 对象、cursor、collection 都需要手动释放。我曾经因为忘记释放 cursor,导致程序跑了几天后内存爆了。
- 线程安全:MongoDB C Driver 的 client 对象是线程安全的,但 cursor 不是。多线程环境下,每个线程应该有自己的 cursor。
- 连接池:默认情况下,每个 client 内部维护了一个连接池。不需要手动管理连接,但要注意不要频繁创建销毁 client。
- 错误处理:
mongoc_cursor_error()一定要检查。网络波动、服务器超时都会导致查询失败,不检查的话程序可能静默地返回空结果。
特别提醒:在嵌入式设备上,MongoDB C Driver 编译出来的体积不小。如果 flash 空间紧张,可以考虑裁剪掉不需要的功能(比如 TLS、SASL 认证等)。
总结
MongoDB C Driver 的核心就三个步骤:初始化、创建客户端、执行查询。其中 mongoc_collection_find_with_opts 是最常用的查询接口,配合 BSON 构造器可以完成各种复杂的查询需求。
我个人觉得,C 语言操作 MongoDB 最大的门槛不是 API 本身,而是 BSON 的构造和内存管理。只要把这两个点搞明白了,剩下的就是熟练度的问题了。
嗯,今天就聊到这里。代码已经贴出来了,建议你动手跑一遍,看看实际效果。