小山最近在疯狂搬砖,老板的项目快结题了TAT。这个项目是基于redis数据库,开发一套面向工业现场的信号采集与数据分析软件。工厂中的机器在实时运行状态下的信号(电流、温度等等),会以JSON文件的形式传输至redis数据库中,我们要做的就是读取这些数据,然后采取多元信息融合算法得到健康指标,监测指标的变化并进行简单的信号分析(FFT或者包络之类的)。

首先,第一步,C++与redis的连接:

小山查了很多资料,翻了github的网站,结合知乎和CSDN论坛里各位大佬的经验,最终是搞定了连接问题。推荐大家几个可以参考的经验,我也会在下面进行一下细致的叙述,确实有一小点复杂,得好好记录一下怕自己忘了,,ԾㅂԾ,,

第一步:redis&C++的连接


redis数据库是一个开源的数据库,方便简单易于使用,广泛的应用与工业互联网这些场合中,支持C++、JAVA等诸多环境的开发,可以说是个“万金油”。它最大的优势同时也是却别与普通数据的地方是,它是一个基于内存的数据库,直接存到工控机的内存里,这样读写速度巨快无比,正是因为快,所以才适合对于控制以及信息获取有很高要求的工业互联网中。

关于redis clients可以参照下面这个网址:

https://redis.io/clients

c++ redis lua脚本 c++实现redis_嵌入式

可以支持的库有很多,综合网上大佬们的经验,hiredis库是使用最多,效果也普遍反映不错的,以下链接为hiredis的下载目录,现在都上传到github上了:

https://github.com/microsoft/hiredis

首先先下载下来,下载的文件有这些

文件README可以用记事本打开,是对于hiredis一些函数的说明和例子

example中有一些例子,包括测试联通、读写list、Hash记录的例程

c++ redis lua脚本 c++实现redis_c++ redis lua脚本_02

hiredis应用到C++中,主要是通过构造静态链接库进行调用的方式进行的

建立联系的方法与步骤如下:

首先,进入文件夹msvs->vs-solutions->vs2015:

c++ redis lua脚本 c++实现redis_c++ redis lua脚本_03

用visual studio 2017打开sln文件,需要生成解决方案,生成好了之后会有bin、tmp这些文件

c++ redis lua脚本 c++实现redis_嵌入式_04

选择 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用的,后面再细说)。

c++ redis lua脚本 c++实现redis_嵌入式_05

放到程序目录下:

c++ redis lua脚本 c++实现redis_c++ redis lua脚本_06

下面开始一系列的配置

(一定要注意在Debug x64下啊!!!!!)

c++ redis lua脚本 c++实现redis_C++&redis_07

 

主要都是在属性里面调整的,哦对了,一定要保证是Debug模式下,x64编译环境

c++ redis lua脚本 c++实现redis_json文件读取_08

包含目录就是我们刚刚弄的include文件夹,新建进去就行,库文件是我们之前生成解决方案时的那四个文件所处的文件夹。

同时在链接器下,点输入,附加依赖项中加入hiredis.lib与win32_interop.lib

c++ redis lua脚本 c++实现redis_C++&redis_09

 

代码生成那一项中运行库,改为MTD(这个涉及到动态静态release和debug的区别)

c++ redis lua脚本 c++实现redis_C++&redis_10

预处理器中的预处理器定义,加上_CRT_SECURE_NO_WARNINGS,之所以加上这个是因为vs2017调用一些函数时,可能会出现warning,有些函数可能有内存方面的风险,加上这一句为了不报错。

c++ redis lua脚本 c++实现redis_C++&redis_11

把预编译头给禁用了,pch.h是一个预编译文件,之后会引入redis相关的c文件,由于版本等其他的原因,如果允许这个pch.h会报错,所以直接关了。

c++ redis lua脚本 c++实现redis_C++&redis_12

最后一步,将hiredis-master文件中的,win32_fixes.c和win32_fixes.h复制到根目录来用:

c++ redis lua脚本 c++实现redis_redis_13

最好改一下win32_fixes.h文件中,库的路径需要改变,打开它就能发现,咱在main函数里是按照include文件的路径来写的,但是win32_fixes.h还没改,因此说会找不到,打开win32_fixes.h,可以发现:

c++ redis lua脚本 c++实现redis_c++ redis lua脚本_14

这都是路径不对,这时候按照include文件夹里的路径改一下就好了:

c++ redis lua脚本 c++实现redis_C++&redis_15

然后进行代码的调试测试,可以在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;
}

控制台的输出:

c++ redis lua脚本 c++实现redis_C++&redis_16

同时redis中的结果(大家可以下一个redisdesktopmanager,蛮好用的):

可以看到,去除了字符串类型的数据 ”hello world“

c++ redis lua脚本 c++实现redis_json文件读取_17

到这里就算连接上了。

第二步:自定义对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里面有个时间戳,是字符串格式的,直接读出来,结果显示:

c++ redis lua脚本 c++实现redis_嵌入式_18

第三步:redis库中存有json文件,利用jsoncpp库进行解析

我理解的json文件,就是一定样式的字符串格式,然后用统一的方法去读取出来,项目中的数据发到redis里面是以json文件的形式输入的,那么我要做的就是解析Json字符串。

利用jsoncpp库进行调用

首先先把jsoncpp库下载,下载链接:

https://github.com/open-source-parsers/jsoncpp。

这里我采用跟他一样的方式,生成源文件然后调用,这种方式最简单最容易,打开jsoncpp.master

在python环境下运行amalgamate,就会在根目录中生成dist文件夹,里面有我们需要的jsoncpp和json头文件,全部复制到开发程序的根目录底下(我是放到了include文件夹下)

c++ redis lua脚本 c++实现redis_json文件读取_19

同时把jsoncpp、json.h和json-forwards.h粘贴到根目录里面,这样调用才没问题:

c++ redis lua脚本 c++实现redis_json文件读取_20

我的json文件是一个复杂的字符串数组,数组里面嵌套数组,这玩的有点像五月天的歌《洋葱》,一点一点拨开我的心,hh,每解开前一套,可以作为新的json字符串,再解开里面的一套,然后通过关键词来拾取数据

我的json文件部分如图,复杂数组嵌套如何读取,可以参照我上面推荐的那篇博文,挺不错的:

c++ redis lua脚本 c++实现redis_json文件读取_21

从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;
}

最终顺利读出来啦:

c++ redis lua脚本 c++实现redis_嵌入式_22

这篇博客就到这里为止啦,砖还是得慢慢搬,希望能帮到大家,如果仁兄们有问题或者觉得有更好的方式的话,欢迎留言告诉我,虚心学习~