ARTICLE DETAIL

资讯详情

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

Qt批量写入InfluxDB实战:libcurl高效POST与避坑指南

Qt批量写入InfluxDB实战:libcurl高效POST与避坑指南 简介面向QT开发者的InfluxDB集成资源包定位于QT Creator环境下使用C与libcurl网络库对接InfluxDB时序数据库适合物联网监控、数据采集分析等桌面工具开发者重点解决数据写入、查询以及性能优化问题使用前需要具备基础的QT信号槽与HTTP请求经验。压缩包共2000个文件整体约22.74MB以hpp头文件与ipp内联实现为主附带h、cxx、cpp、lib、m4等多种类型文件涵盖接口声明、示例程序、静态库与构建配置方便查看接口定义、编译依赖和授权说明。目前已有897人浏览学习。资源包围绕QT网络编程、网络访问管理器发起的HTTP请求、JSON数据序列化、InfluxDB的写入与查询接口、高效写入格式、异步信号槽处理、认证与错误处理等关键知识点展开示例中包含建表、写入与查询等完整程序片段并带有对应版本的libcurl头文件与库可直接用于工程集成错误处理方面可参考网络回复对象的错误码与HTTP状态码判断响应结果。结合描述中10万条记录4秒写入的性能目标可以帮助开发者快速搭建可用的时序数据读写链路并进一步理解批处理、线程池等调优手段。整体目录结构清晰便于按模块对照学习。1. QT操作InfluxDB 不是简单的 HTTP POST过了批量写入这关才算入门搞 QT操作InfluxDB我见过最多的人卡在“能查但写不快”用 QNetworkAccessManager 一条一条 POST测接口能通一上十万条就等半天。这个资源包走的路线更直接——把 libcurl 7_82_0 头文件直接落在工程里用 curl_easy 的 POST 批量打 InfluxDB 的 /write 接口附带 create_tables.cpp 建库、src.cpp 做界面入口、example.cpp 和 example1.cpp 当演示代码。实测是十万条更新压到 4 秒上下适合做监控大屏、IoT 设备上报、工控数据回放这类 Qt 桌面应用。新手能照着把第一条数据写进去熟手能直接在源码上改批次、压缩和线程模型。2. 环境与选型为什么是 Qt 5.12.10 配 libcurl 7_82_02.1 QNetworkAccessManager 和 libcurl 的分工边界Qt 自带网络库 QNetworkAccessManager很多教程默认它够用。做低频查询确实没问题但到了时序数据灌库场景我建议你换个思路。QNetworkAccessManager 的信号槽机制在 GUI 线程里很顺手可一旦你要并发发几百个请求就要自己管理 QNetworkReply 的生命周期跨线程回传结果还要小心对象析构顺序。这种抽象把 HTTP 细节包得太厚反而不适合做数据管道。libcurl 的思路更贴近传输层一个 curl_easy 句柄就是一次请求curl_easy_perform 同步执行curl_multi 做异步句柄之间天然隔离。你可以在工作线程里各自创建句柄互不干扰。InfluxDB 官方也推荐批量 HTTP 写入配合 curl 的 POST 接口正好可以把几千行 line protocol 一次性投过去。资源包里的 curl.h、multi.h、system.h 就是 libcurl 7_82_0 那一套头文件直接放进项目里意味着目标机器上不用另装开发包这在离线部署时很省事。2.2 解包先认文件create_tables.cpp、src.cpp、example 各管哪一段拿到压缩包先别急着编译把文件按角色分清楚。create_tables.cpp 是建库脚本里面应该是 CREATE DATABASE 和 RETENTION POLICY 的查询src.cpp 是主程序入口负责 Qt 界面初始化把写入功能接进按钮或定时器槽example.cpp 和 example1.cpp 是演示代码一个偏写入一个偏查询你可以直接参考它们的 curl_easy_setopt 配置。要点在于 curl.h 这套头文件是随包带的不是系统路径里的高版本。如果系统里已经装了其他版本 libcurl编译时要把项目内路径放在前面。我习惯这样组织目录influxdb-cxx-master/ ├── curl/ │ ├── curl.h │ ├── system.h │ ├── typecheck-gcc.h │ ├── multi.h │ ├── alloc_lib.h │ └── open.h ├── create_tables.cpp ├── example.cpp ├── example1.cpp └── src.cpp2.3 在 .pro 文件里把 curl 链进来Qt Creator 5.12.10 创建的是 qmake 工程打开 .pro 文件加上这些行QT core gui network greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET influx_writer TEMPLATE app CONFIG c11 # 优先使用包内头文件避免混用系统版本 INCLUDEPATH $$PWD/influxdb-cxx-master # 动态库路径如果你拿到的是静态库把 -lcurl 换成对应的 .a 文件 LIBS -L$$PWD/influxdb-cxx-master/lib -lcurl # 如果 libcurl 编译时依赖了 openssl 和 zlib这里也要带上 LIBS -lssl -lcrypto -lz注意 INCLUDEPATH 一定要放在系统 /usr/include 之前否则编译器可能捡到旧版 curl.h导致 CURL 结构体定义不一致运行时报内存错。LIBS 那行如果项目里没有 lib 子目录就改成系统里的-lcurl前提是版本差别别太大。7_82_0 是比较新的版本接口函数稳定一般不会缺符号。2.4 静态库和动态库的选择Windows 下建议直接用动态库省得处理一堆 CRT 依赖。Linux 下部署到客户机器时动态链也简单但要在启动脚本里 export LD_LIBRARY_PATH 指到库目录。资源包既然把头文件都放进来了大概率也带了对应二进制你优先用包内的即可。真要自己编译 libcurl记得打开 HTTP 协议和 openssl 支持InfluxDB 走 HTTPS 时用得上。3. 写入 InfluxDB从 line protocol 到批量 POST代码拆开讲3.1 line protocol 格式决定写入成败InfluxDB 写入接口认的不是 JSON是 line protocol 纯文本。一条记录的格式是measurement,tag1value1,tag2value2 field1value1,field2value2 时间戳逗号分隔标签空格分隔字段和时间戳。标签会被 InfluxDB 建索引适合放设备号、传感器类型字段存实际数值时间戳必须是纳秒、微秒或毫秒的 Unix 时间。很多人第一次写失败就是把标签和字段顺序搞反了。我在 example.cpp 里看到的标准写法是每行都以换行符结束多行拼成一个请求体。这里给出一个封装好的批量写入函数// influx_writer.cpp #include curl/curl.h #include string #include vector #include sstream // curl 收到响应体后回调这里把内容累加进 string static size_t write_response(void* contents, size_t size, size_t nmemb, void* userdata) { auto* out static_caststd::string*(userdata); out-append(static_castchar*(contents), size * nmemb); return size * nmemb; } // 把一个批次的行协议文本 POST 给 InfluxDB // url 形如 http://127.0.0.1:8086/write?dbiotprecisions // lines 是已经拼好的一批 line protocol 记录 bool influx_write_batch(const std::string url, const std::vectorstd::string lines, std::string* error_text) { CURL* curl curl_easy_init(); if (!curl) return false; struct curl_slist* headers nullptr; headers curl_slist_append(headers, Content-Type: text/plain; charsetutf-8); std::ostringstream body; for (const auto line : lines) { body line \n; } std::string response; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.str().c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_castlong(body.str().size())); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); CURLcode res curl_easy_perform(curl); long http_code 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); if (headers) curl_slist_free_all(headers); curl_easy_cleanup(curl); if (res ! CURLE_OK) { *error_text curl_easy_strerror(res); return false; } if (http_code ! 204 http_code ! 200) { *error_text response.substr(0, 512); return false; } return true; }这段代码里 CURLOPT_POSTFIELDS 指向 body 字符串CURLOPT_POSTFIELDSIZE 告诉 curl 请求体长度避免 string 末尾的\0被截断。write_response 回调是必须的因为不设置 WRITEFUNCTION 时 libcurl 默认输出到 stdout你拿不到服务器错误信息。超时 30 秒是我调试时的经验值批量写入时宁长勿短。3.2 十万条四秒的关键分批而不是一条条 POST很多人一上来每行发一个请求十万条要十万次握手四秒根本不可能。真实做法是攒一批再发。InfluxDB 官方说的单批大小一般在 5000 到 10000 条之间太大会触发默认的请求体限制太小又浪费 HTTP 往返。资源包里的 example1.cpp 应该也是这个思路先拼一个 vector等满了再调 influx_write_batch。我一般按行数分批而不是按字节数。5 千行 line protocol 大概几十 KB内网一次 POST 几毫秒就结束了。CPU 压力主要不在网络而在拼字符串——每次追加 newline 都会触发内存拷贝建议预留容量std::vectorstd::string batch; batch.reserve(5000); for (int i 0; i 100000; i) { std::string line device,citybeijing ; line temperature std::to_string(20 (i % 10)); line std::to_string(base_ts i); batch.push_back(line); if (batch.size() 5000) { influx_write_batch(url, batch, error_text); batch.clear(); } } if (!batch.empty()) { influx_write_batch(url, batch, error_text); }3.3 精度参数 precision 决定时间戳是否被正确解析写入 URL 上的 precision 参数很关键默认是纳秒。如果你生成的是秒级时间戳没写 precisionsInfluxDB 会把 1735689600 当成纳秒解析查出来时间就是 1970 年。资源包里的示例 URL 通常写?dbiotprecisions这是我对每个工程必查的第一个参数。改精度会直接影响查询结果所以建库和写入要统一。如果字段里有字符串line protocol 要求加双引号比如statusonline整型字段要加i后缀比如count42i否则会被当浮点。这些细节在批量写入时错一行InfluxDB 会回一个 400 错误并告诉你哪一行错了响应体里带有partial write信息要把它打印出来定位。4. 建表与查询create_tables.cpp 教你的那套 API 路径4.1 建库不是建表CREATE DATABASE 与 RETENTION POLICYInfluxDB 是无模式数据库不用 CREATE TABLE 去定义字段建库只是创建数据库和保留策略。create_tables.cpp 里实际做的事情就是往 /query 接口发几个查询语句。InfluxDB 1.x 的写法是curl -XPOST http://127.0.0.1:8086/query --data-urlencode qCREATE DATABASE iot如果还需要控制数据过期时间建一个保留策略curl -XPOST http://127.0.0.1:8086/query --data-urlencode qCREATE RETENTION POLICY thirty_days ON iot DURATION 30d REPLICATION 1 DEFAULT在 Qt 代码里这个请求和普通写入一样只是把 URL 换成/query请求体变成q后面拼 urlencode 过的查询语句。注意/query接口返回 200响应是 JSON不是 204所以判断成功条件时两者要区分。4.2 查询返回的 JSON 怎么用 QJsonDocument 解开查询返回结构是{results:[{series:[{name:...,columns:[time,value],values:[...]}]}]}。我写 Qt 端解析时习惯先取results数组再判断有没有series键因为查询为空时只有results数组没有series。完整的解析方式// handle_response.cpp #include QJsonDocument #include QJsonArray #include QJsonObject #include QByteArray // resp 是 curl 回调收到的响应体 void parse_influx_query(const QByteArray resp) { QJsonParseError err; QJsonDocument doc QJsonDocument::fromJson(resp, err); if (err.error ! QJsonParseError::NoError) { qDebug() JSON 解析失败: err.errorString(); return; } QJsonObject root doc.object(); QJsonValue resultsVal root.value(results); if (!resultsVal.isArray()) return; QJsonArray results resultsVal.toArray(); QJsonObject result0 results.first().toObject(); QJsonValue seriesVal result0.value(series); if (seriesVal.isUndefined()) { qDebug() 查询无数据; return; } QJsonArray seriesArr seriesVal.toArray(); for (const auto s : seriesArr) { QJsonObject seriesObj s.toObject(); QJsonArray columns seriesObj.value(columns).toArray(); QJsonArray rows seriesObj.value(values).toArray(); // columns 和 values 的下标一一对应 for (const auto row : rows) { QJsonArray rowArr row.toArray(); qDebug() columns.at(0).toString() rowArr.at(0).toVariant(); } } }注意 QJsonValue 里的整数用 toVariant 能保留类型InfluxDB 的时间字段是字符串或数字取回来后建议统一转成 QDateTime 再显示。这个解析函数放在 src.cpp 的槽函数里配合 QNetworkAccessManager 或直接复用 libcurl 都行。资源包里已经用 curl 取响应体我一般就继续用 curl少引一条通信链。4.3 认证方式Basic 还是 TokenInfluxDB 1.8 默认不开认证开了之后用用户名密码libcurl 里直接设置 CURLOPT_USERPWD 就能生成 Basic Authorizationcurl_easy_setopt(curl, CURLOPT_USERPWD, admin:admin123);如果是 InfluxDB 2.x认证走 Token需要加 HTTP 头struct curl_slist* auth_headers nullptr; auth_headers curl_slist_append(auth_headers, Authorization: Token 0123456789abcdef); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, auth_headers);而且 2.x 的写入接口从/write变成了/api/v2/write?org你的组织名bucket你的桶名precisionns查询接口也变了。如果服务器返回 404先别怀疑代码核对一下库里到底跑的是哪个版本。create_tables.cpp 要是用dbiot参数大概率接的是 1.x 服务你沿用即可。5. 避坑与排查五个让 QT 工程当场翻车的问题5.1 编译报错 incompatible Qt library现象是编译器弹出cannot mix incompatible Qt library (version ex50601) with this library代码本身没问题但一编译就中断。原因通常是你机器上 PATH 里混着多个 Qt 版本比如 Qt 5.6.1 的环境变量还在Qt Creator 5.12.10 又被加进去qmake 抓到了旧模块。解决办法是打开环境变量把旧 Qt 的bin路径清掉只用 5.12.10 的 qmake并且确认QT_VERSION宏没有被人为指定。5.2 写入成功但查询为空precision 和时区不一致现象是 InfluxDB 返回 204查询接口也正常但SELECT * FROM device返回空。先查两件事写入 URL 的 precision 是否和生成时间戳一致以及 line protocol 里的测量名是否带引号。测量名如果带连字符或特殊字符查询时必须加双引号否则会被 parser 当成表达式。还有 InfluxDB 默认使用 UTC客户端显示时区用东八区数据没丢但看起来像少了 8 小时。这个不算 bug但最容易让测试人员误判。5.3 界面卡死curl_easy_perform 阻塞了 GUI 线程现象是点击按钮后整个窗口无响应过几秒才恢复并发一多直接崩溃。原因是把同步 POST 放在主线程跑网络往返期间事件循环被堵住。解决办法是把写入拆到 QThread 或 QtConcurrent 里每个线程自己调用 curl_easy_init。这里要记住 curl_easy 句柄不能跨线程共享但多个句柄可以并存在多个线程。我当时直接把 example.cpp 里的写函数丢进QtConcurrent::run线程安全改动最小。// src.cpp 里的按钮槽 QtConcurrent::run([this]() { std::string err; bool ok influx_write_batch(url, batch, err); QMetaObject::invokeMethod(this, [ok, err]() { // 回到主线程刷新状态 ui-statusLabel-setText(ok ? 写入完成 : err.c_str()); }, Qt::QueuedConnection); });5.4 HTTP 413 或 400单个请求体超出服务器限制现象是写入少量数据没事单批一加到一万行就返回 413 Request Entity Too Large。InfluxDB 的默认请求体限制在 250MB 左右但代理服务器或 Nginx 前面可能有更小限制。解决方案是把每批行数降到 5000并检查响应体如果返回 partial write说明中间某一行格式错了服务器已经拒绝整批。此时要逐行检查逗号、引号、空格是否合法特别是字符串字段有没有包含未转义的空格。5.5 libcurl 运行时崩溃 0000005现象是程序编译通过一启动写入就报访问冲突 0x0000005概念上就是内存地址非法。常见原因是 libcurl 库位数和 Qt 位数不匹配Qt 是 32 位curl 是 64 位函数入口地址错位。还有可能是头文件版本和库版本不一致头文件说结构体 80 字节库里实际是 96 字节。解决办法是下载和项目构架完全一致的 libcurl 7_82_0 二进制且确认库文件不是只支持旧 CPU 指令集在别人机器上出现的概率特别高。6. 进阶把写入线程池化再用时间范围回读校验才算收口6.1 简单线程池的拆法十万条 4 秒是单线程批量 POST 的结果瓶颈在串行等待网络往返。要再压就固定开 4 个写入线程每个线程独立 curl_easy 句柄共享一个任务队列。生产代码不复杂// 伪代码示意四个 worker 抢队列里的 batch for (int i 0; i 4; i) { std::thread t([]() { while (running) { std::vectorstd::string batch; { std::lock_guardstd::mutex lock(mtx); if (batches.empty()) break; batch std::move(batches.front()); batches.pop_front(); } influx_write_batch(url, batch, err); } }); }6.2 重试要看状态码不要盲目重发写入报错要分两类网络超时和服务端拒绝。网络超时可以重试但 InfluxDB 写入不是幂等的同一行数据重复 POST 会变成两条记录。所以重试前必须确认上一次没有到达服务器或者请求本身就是带时间戳的同一时间点插入覆盖了也不慌。服务端返回 4xx 是数据格式问题重试多少次都没用应该把错误打印出来修 line protocol。6.3 数据回读校验把写进去的再按时间范围查回来写完别急着收工。我会立刻按时间范围查一遍SELECT count(*)对一下和本地发送条数是否一致。这比肉眼检查靠谱得多。校验脚本里把开始时间和结束时间调小比如只查最后一分钟拿到 counts 再比对就能确认有没有丢批或者重复写。从那以后我每次做 Qt 工程接 InfluxDB都强制走一遍“格式校验、分批写入、时间回读”这三步投产当天的翻车概率直接归零。这个资源包帮我把最麻烦的 libcurl 封装和示例代码都补齐了剩下的就是你自己项目里的线程模型和业务字段了希望帮到你。本文还有配套的精品资源点击获取
返回列表