小山最近在疯狂搬砖,老板的项目快结题了TAT。这个项目是基于redis数据库,开发一套面向工业现场的信号采集与数据分析软件。工厂中的机器在实时运行状态下的信号(电流、温度等等),会以JSON文件的形式传输至redis数据库中,我们要做的就是读取这些数据,然后采取多元信息融合算法得到健康指标,监测指标的变化并进行简单的信号分析(FFT或者包络之类的)。
首先,第一步,C++与redis的连接:
小山查了很多资料,翻了github的网站,结合知乎和CSDN论坛里各位大佬的经验,最终是搞定了连接问题。推荐大家几个可以参考的经验,我也会在下面进行一下细致的叙述,确实有一小点复杂,得好好记录一下怕自己忘了,,ԾㅂԾ,,
第一步:redis&C++的连接
redis数据库是一个开源的数据库,方便简单易于使用,广泛的应用与工业互联网这些场合中,支持C++、JAVA等诸多环境的开发,可以说是个“万金油”。它最大的优势同时也是却别与普通数据的地方是,它是一个基于内存的数据库,直接存到工控机的内存里,这样读写速度巨快无比,正是因为快,所以才适合对于控制以及信息获取有很高要求的工业互联网中。
关于redis clients可以参照下面这个网址:
可以支持的库有很多,综合网上大佬们的经验,hiredis库是使用最多,效果也普遍反映不错的,以下链接为hiredis的下载目录,现在都上传到github上了:
https://github.com/microsoft/hiredis
首先先下载下来,下载的文件有这些
文件README可以用记事本打开,是对于hiredis一些函数的说明和例子
example中有一些例子,包括测试联通、读写list、Hash记录的例程
hiredis应用到C++中,主要是通过构造静态链接库进行调用的方式进行的
建立联系的方法与步骤如下:
首先,进入文件夹msvs->vs-solutions->vs2015:
用visual studio 2017打开sln文件,需要生成解决方案,生成好了之后会有bin、tmp这些文件
选择 Debug x64下生成,右键分别点击hiredis与 win32_interop
生成好了之后,在这个目录下会有bin文件,bin->x64->Debug,可以看到生成的四个文件(lib、pdp)格式的,这些就是库文件。
接着,开始连接,创建一个C++windows控制台程进行联通测试,进行链接库的连接:
将hirdeis-master原目录下的C++ Header file文件以及adapters目录下以及msvs\win32_interop文件都拷贝到include文件中,放到控制台的目录里,按照目录相同的搞进来(文件名可以自己设置,引用的时候改一下路径就行了,俩JSON文件是我后面解析json用的,后面再细说)。
放到程序目录下:
下面开始一系列的配置
(一定要注意在Debug x64下啊!!!!!)
主要都是在属性里面调整的,哦对了,一定要保证是Debug模式下,x64编译环境
包含目录就是我们刚刚弄的include文件夹,新建进去就行,库文件是我们之前生成解决方案时的那四个文件所处的文件夹。
同时在链接器下,点输入,附加依赖项中加入hiredis.lib与win32_interop.lib
代码生成那一项中运行库,改为MTD(这个涉及到动态静态release和debug的区别)
预处理器中的预处理器定义,加上_CRT_SECURE_NO_WARNINGS,之所以加上这个是因为vs2017调用一些函数时,可能会出现warning,有些函数可能有内存方面的风险,加上这一句为了不报错。
把预编译头给禁用了,pch.h是一个预编译文件,之后会引入redis相关的c文件,由于版本等其他的原因,如果允许这个pch.h会报错,所以直接关了。
最后一步,将hiredis-master文件中的,win32_fixes.c和win32_fixes.h复制到根目录来用:
最好改一下win32_fixes.h文件中,库的路径需要改变,打开它就能发现,咱在main函数里是按照include文件的路径来写的,但是win32_fixes.h还没改,因此说会找不到,打开win32_fixes.h,可以发现:
这都是路径不对,这时候按照include文件夹里的路径改一下就好了:
然后进行代码的调试测试,可以在hiredis的example用正规代码段,我是简单的写了一些,就写了比较短的作为演示:
具体redis的指令,挺容易上手的,推荐大家一个网站看一下redis的指令:
https://www.runoob.com/redis/redis-tutorial.html
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include "include\hiredis\hiredis.h" //这个是按照include文件路径来的
#define NO_QFORKIMPL
#include "include\win32_interop\win32_fixes.h" //这个是按照include文件路径来的
#include <fstream>
#include <cassert>
using namespace std;
int main() {
redisContext *c;
redisReply *reply;
const char *hostname = "127.0.0.1"; //这里是连接,本地ip:127.0.0.1
int port = 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
///* PING server */
reply = (redisReply*)redisCommand(c, "ping");
printf("ping: %s\n", reply->str);
freeReplyObject(reply);
reply = (redisReply*)redisCommand(c, "set %s %s", "foo", "hello world");
printf("set: %s\n", reply->str);
freeReplyObject(reply);
reply = (redisReply*)redisCommand(c, "GET foo");
char *a;
a = reply->str;
printf("GET foo: %s\n", a);
freeReplyObject(reply);
//连接redis不同的数据库 db1,默认是连接db0 切换数据库
reply = (redisReply*)redisCommand(c, "select 1");
printf("连接到db1: %s\n", reply->str);
freeReplyObject(reply);
redisFree(c);
system("pause");
return 0;
}
控制台的输出:
同时redis中的结果(大家可以下一个redisdesktopmanager,蛮好用的):
可以看到,去除了字符串类型的数据 ”hello world“
到这里就算连接上了。
第二步:自定义对hiredis进行二次封装,方便使用
我想着以后用的方便,用源文件类的方式对hiredis中的指令进行了二次封装,这里写了一个取出字符串的demo,源文件类的方式需要写一个.h文件和一个.cpp文件,.h头文件用来声明变量和类,cpp文件用来定义函数细节
下面是我的头文件:
#pragma once
#include <string.h>
#include <iostream>
#include "include\hiredis\hiredis.h"
using namespace std;
class jstry
{
private:
const char *a;
redisContext *c; //这直接是一个结构体 hiredis里面定义好了的
redisReply *reply; //这直接是一个结构体 hiredis里面定义好了的
public:
char *getredisdata(const char *a, redisContext *c);
//想实现的函数效果是,输入一个cmd指令,得到字符串
这是我的.cpp文件:
#include "jstry.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include\hiredis\hiredis.h"
//这个是用来读取字符串的
using namespace std;
//写函数时,需要些函数的输出类型
char *jstry::getredisdata(const char *a, redisContext *c)
{
reply = (redisReply*)redisCommand(c,a);
return reply->str;
freeReplyObject(reply);
}
然后再复制到目录里面进行调用就可以了
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include "jstry.h"
#include "include\hiredis\hiredis.h"
#define NO_QFORKIMPL
#include "include\win32_interop\win32_fixes.h"
#include "include/json/json.h"
#include <fstream>
#include <cassert>
using namespace std;
int main() {
redisContext *c;
redisReply *reply;
const char *hostname = "127.0.0.1";
int port = 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
///* PING server */
reply = (redisReply*)redisCommand(c, "ping");
printf("ping: %s\n", reply->str);
freeReplyObject(reply);
//连接redis不同的数据库 db1,默认是连接db0 切换数据库
reply = (redisReply*)redisCommand(c, "select 1");
printf("连接到db1: %s\n", reply->str);
freeReplyObject(reply);
//用自己写的类读取字符串
jstry A;
const char *cmd; //redis这个指令是 const char形式的
cmd = "GET TimeStamp";
char *time;
time = A.getredisdata(cmd, c);
printf("outcome: %s\n", time);
redisFree(c);
system("pause");
return 0;
}
我的redis里面有数据,db1里面有个时间戳,是字符串格式的,直接读出来,结果显示:
第三步:redis库中存有json文件,利用jsoncpp库进行解析
我理解的json文件,就是一定样式的字符串格式,然后用统一的方法去读取出来,项目中的数据发到redis里面是以json文件的形式输入的,那么我要做的就是解析Json字符串。
利用jsoncpp库进行调用
首先先把jsoncpp库下载,下载链接:
https://github.com/open-source-parsers/jsoncpp。
这里我采用跟他一样的方式,生成源文件然后调用,这种方式最简单最容易,打开jsoncpp.master
在python环境下运行amalgamate,就会在根目录中生成dist文件夹,里面有我们需要的jsoncpp和json头文件,全部复制到开发程序的根目录底下(我是放到了include文件夹下)
同时把jsoncpp、json.h和json-forwards.h粘贴到根目录里面,这样调用才没问题:
我的json文件是一个复杂的字符串数组,数组里面嵌套数组,这玩的有点像五月天的歌《洋葱》,一点一点拨开我的心,hh,每解开前一套,可以作为新的json字符串,再解开里面的一套,然后通过关键词来拾取数据
我的json文件部分如图,复杂数组嵌套如何读取,可以参照我上面推荐的那篇博文,挺不错的:
从redis中读取json文件的代码:
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include "jstry.h"
#include "include\hiredis\hiredis.h"
#define NO_QFORKIMPL
#include "include\win32_interop\win32_fixes.h"
#include "include/json/json.h"
#include <fstream>
#include <cassert>
using namespace std;
int main() {
redisContext *c;
redisReply *reply;
const char *hostname = "127.0.0.1";
int port = 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
///* PING server */
reply = (redisReply*)redisCommand(c, "ping");
printf("ping: %s\n", reply->str);
freeReplyObject(reply);
//连接redis不同的数据库 db1,默认是连接db0 切换数据库
reply = (redisReply*)redisCommand(c, "select 1");
printf("连接到db1: %s\n", reply->str);
freeReplyObject(reply);
//用自己写的类读取字符串
jstry A;
const char *cmd; //redis这个指令是 const char形式的
cmd = "GET TimeStamp";
char *time;
time = A.getredisdata(cmd, c);
printf("outcome: %s\n", time);
//读取JSON字符串
reply = (redisReply*)redisCommand(c, "LRANGE Dynamic:SampleData 0 2");
string jsondata;
jsondata = reply->element[1]->str; //json是一个字符串
printf("电流数据: %s\n", reply->element[1]->str);
freeReplyObject(reply);
Json::CharReaderBuilder readerBuilder;
Json::Value root, item, kind, current;
std::unique_ptr<Json::CharReader> const jsonReader(readerBuilder.newCharReader());
bool res;
JSONCPP_STRING errs;
res = jsonReader->parse(jsondata.c_str(), jsondata.c_str() + jsondata.length(), &root, &errs);
if (!res || !errs.empty()) {
std::cout << "parseJson err. " << errs << std::endl;
}
//读取CURRENT数组数据
int currentdata[100];
item = root["Data"];
kind = item["1"];
current = kind["CURRENT"];
for (int i = 0; i < current.size(); ++i) {
currentdata[i] = current[i].asInt();
}
printf(" 电流波形数据为:\n");
for (int i = 0; i < current.size(); ++i) {
printf(" %d\n", currentdata[i]);
}
redisFree(c);
system("pause");
return 0;
}
最终顺利读出来啦:
这篇博客就到这里为止啦,砖还是得慢慢搬,希望能帮到大家,如果仁兄们有问题或者觉得有更好的方式的话,欢迎留言告诉我,虚心学习~