C++Mysql8.0数据库跨平台编程实战(下)

C++Mysql8.0数据库跨平台编程实战(下)

  • 第六章 跨平台中文乱码问题和mysql锁
    • 1、MySQLAPIC++封装策略和方法说明
    • windows上字符集gbk和utf8互转
      • 开始写代码把测试框架搭起来
    • linux上字符集GBK和UTF8互转
    • ZPMysql库添加字符集转换函数并测试GBK
      • 插入utf-8的数据
        • 我们把代码在linux运行看看
      • 插入gbk的数据
    • 简易获取数据的接口GetResult实现
    • mysql的表锁和行锁代码示例购票竞争
  • 第七章 日志审计系统项目实战和课程总结
    • 日志审计系统项目模块分析
    • 日志审计系统Agent模块项目
    • 日志审计系统Center模块项目
      • Center模块安装配置和数据初始化
      • Center审计策略表安装和策略添加
      • 完成Center的添加设备
      • Center主循环获取到Agent发送的最新事件


第六章 跨平台中文乱码问题和mysql锁

1、MySQLAPIC++封装策略和方法说明

C++Mysql8.0数据库跨平台编程实战(下)_第1张图片

C++Mysql8.0数据库跨平台编程实战(下)_第2张图片

C++Mysql8.0数据库跨平台编程实战(下)_第3张图片

C++Mysql8.0数据库跨平台编程实战(下)_第4张图片

C++Mysql8.0数据库跨平台编程实战(下)_第5张图片

我们希望一套代码到处编译,所以这里我们选择了系统的API,系统API也比较简单:
C++Mysql8.0数据库跨平台编程实战(下)_第6张图片

windows上字符集gbk和utf8互转

我们先说明一下我们怎么测这个案例,就是每次你做一个案例,就要想怎么测。

  • UTF-8到GBK的转换
    第一个就是UTF-8转GBK的话,GBK可以直接在控制台显示,这个跟系统有关,我们系统控制台默认是GBK,所以说UTF-8转GBK很简单,我们直接cout打印就行了;
    那么UTF-8这种编码的字符串从哪里来呢?我们直接把这个代码字符串前面加一个u8就可以了,但这样呢还是会有问题的,并不是说很兼容;我们直接把代码文件设成utf-8的,但是有的机器上这样设置后,当编译成字符串的时候还是GBK,也就是说它编译的时候做了转换;
    所以这个时候,我们比较靠谱的方法就是说,在字符串前面加一个u8,例如u8"我这个是utf-8的字符串",告诉编译器我们要把所标识的这个字符串转成utf-8,就跟你在linux当中编译的时候指定这个代码用什么编译一样。

  • GBK到UTF-8的转换
    我们默认的就是GBK,你不加u8,并且把代码转成GBK的,把我们的代码文件转成GBK的,然后再把把转化成UTF-8的;
    而UTF-8怎么测呢,打印是输出不了的,在Linux当中可以输出(linux默认输出是utf-8的),在windows当中不行,那怎么办呢?几个方案:
    (1)把UTF-8写到文件里面去;
    (2)我们前面不是做了UTF-8转GBK么,我们再转一遍把它输出就可以了。

开始写代码把测试框架搭起来

C++Mysql8.0数据库跨平台编程实战(下)_第7张图片

C++Mysql8.0数据库跨平台编程实战(下)_第8张图片

我们可以看到控制台默认是GBK的。

C++Mysql8.0数据库跨平台编程实战(下)_第9张图片

C++Mysql8.0数据库跨平台编程实战(下)_第10张图片

C++Mysql8.0数据库跨平台编程实战(下)_第11张图片

有的vs版本(例如vs2017)的默认编码是UTF-8的,可以从上图看到,我们这里的vs2019默认编码是GBK的。

当然你可以基于其他的工具(例如notepad++、UltraEdit推荐):
C++Mysql8.0数据库跨平台编程实战(下)_第12张图片

我们把代码换成ANSI编码的话,代码里面的中文注释都成乱码了:
C++Mysql8.0数据库跨平台编程实战(下)_第13张图片

我们可以点击下面的菜单:转为ANSI编码,就可以把代码转为ANSI编码格式了。

// test_gbk_utf8.cpp
//

#include 
#include 
#include 

using namespace std;

string UTF8ToGBK(const char* data)
{
	string gbk = "";

	// 1. UTF-8转为unicode		其实unicode对windows来说呢就是utf-16,就是用宽字节来存储的
	
	// 1.1 统计转换后的字节数
	int len = MultiByteToWideChar(CP_UTF8,	// 转换的格式
						0,			// 默认的转换方式
						data,		// 输入的字节数据
						-1,			// 输入的字符串大小 -1就是找\0结束
						nullptr,	// 输出
						0// 输出的空间大小
		);
	if (len <= 0)
		return gbk;
	wstring udata;
	udata.resize(len);
	// 转换为unidoce了
	MultiByteToWideChar(CP_UTF8, 0, data, -1, (wchar_t*)udata.data(), len);

	// 2. unicode转GBK
	// 2.1 统计转换后的字节数
	len = WideCharToMultiByte(CP_ACP, 0, (wchar_t*)udata.data(), -1, nullptr, 0, 
		nullptr,	// 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的
		0		// 是否使用默认替代
	);
	if (len <= 0)
		return gbk;
	gbk.resize(len);
	WideCharToMultiByte(CP_ACP, 0, (LPWSTR)udata.data(), -1, (char*)gbk.data(), len, nullptr, 0);

	return gbk;
}

string GBKToUTF8(const char* data)
{
	string utf8 = "";

	// GBK转unicode
	// 1.1 统计转换后的字节数
	int len = MultiByteToWideChar(CP_ACP,	// 转换的格式
		0,			// 默认的转换方式
		data,		// 输入的字节数据
		-1,			// 输入的字符串大小 -1就是找\0结束
		nullptr,	// 输出
		0// 输出的空间大小
	);
	if (len <= 0)
		return utf8;
	wstring udata;
	udata.resize(len);
	// 转换为unidoce了
	MultiByteToWideChar(CP_ACP, 0, data, -1, (wchar_t*)udata.data(), len);

	// 2. unicode转UTF-8
	// 2.1 统计转换后的字节数
	len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)udata.data(), -1, nullptr, 0,
		nullptr,	// 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的
		0		// 是否使用默认替代
	);
	if (len <= 0)
		return utf8;
	utf8.resize(len);
	WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)udata.data(), -1, (char*)utf8.data(), len, nullptr, 0);

	return utf8;
}

int main()
{
	// 为了保证下面的测试正确,先确保我们的代码是GBK或者ANSI的
	std::cout << "Hello World 测试!\n";

	// 1、测试UTF-8转GBK
	cout << UTF8ToGBK(u8"测试UTF-8转GBK") << endl;

	// 2、测试GBK到UTF-8的转换
	string utf8 = GBKToUTF8("测试GBK转UTF-8再转为GBK");
	cout << "luan ma : utf8 = " << utf8 << endl;
	cout << "GBK = " << UTF8ToGBK(utf8.c_str()) << endl;

	return 0;
}

linux上字符集GBK和UTF8互转

C++Mysql8.0数据库跨平台编程实战(下)_第14张图片

我们用UE把代码转成UTF-8格式:
C++Mysql8.0数据库跨平台编程实战(下)_第15张图片
在windows下要选择utf-8(有BOM)。

// test_gbk_utf8_linux makefile

test:test_gbk_utf8_linux.cpp
	g++ $^ -o $@
// test_gbk_utf8.cpp
//

#include 
#include 

#ifdef _WIN32
#include 
#else
#include 
#include 
#endif
using namespace std;

#ifndef _WIN32
static size_t Convert(char* from_cha, char* to_cha, char* in, size_t inLen, char* out, size_t outLen)
{
	// 转换上下文
	iconv_t cd;
	cd = iconv_open(to_cha, from_cha);
	if(cd == 0)
		return -1;	// 如果字符编码打开失败,直接返回-1
	memset(out, 0, outlen);
	char** pin = &in;
	char** pout = &out;
	cout << "in = " << in << endl;
	cout << "inLen = " << inLen << endl;
	cout << "outLen = " << outLen << endl;
	// 返回转换字节的数量,但是转GBK时经常不正确 >=0就成功
	size_t re = iconv(cd, pin, &inLen, pout, &outLen);
	if(re < 0)
	{
		cout << "iconv failed! re = " << (int)re << endl;
		return -1;
	}
	cout << "iconv success! re = " << (int)re << endl;

	if(cd != 0)
		iconv_close(cd);
	return re;
}
#endif

string UTF8ToGBK(const char* data)
{
	string re = "";

	// 1. UTF-8转为unicode		其实unicode对windows来说呢就是utf-16,就是用宽字节来存储的
#ifdef _WIN32	
	// 1.1 统计转换后的字节数
	int len = MultiByteToWideChar(CP_UTF8,	// 转换的格式
						0,			// 默认的转换方式
						data,		// 输入的字节数据
						-1,			// 输入的字符串大小 -1就是找\0结束
						nullptr,	// 输出
						0// 输出的空间大小
		);
	if (len <= 0)
		return re;
	wstring udata;
	udata.resize(len);
	// 转换为unidoce了
	MultiByteToWideChar(CP_UTF8, 0, data, -1, (wchar_t*)udata.data(), len);

	// 2. unicode转GBK
	// 2.1 统计转换后的字节数
	len = WideCharToMultiByte(CP_ACP, 0, (wchar_t*)udata.data(), -1, nullptr, 0, 
		nullptr,	// 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的
		0		// 是否使用默认替代
	);
	if (len <= 0)
		return re;
	re.resize(len);
	WideCharToMultiByte(CP_ACP, 0, (LPWSTR)udata.data(), -1, (char*)re.data(), len, nullptr, 0);
#else
	// linux部分
	re.resize(1024);
	int inLen = strlen(data);
	Convert((char*)"utf-8", (char*)"gbk", (char*)data, inLen, (char*)re.data(), re.size());
	int outLen = strlen(re.data());
	re.resize(outLen);
#endif
	return re;
}

string GBKToUTF8(const char* data)
{
	string re = "";
#ifdef _WIN32
	// GBK转unicode
	// 1.1 统计转换后的字节数
	int len = MultiByteToWideChar(CP_ACP,	// 转换的格式
		0,			// 默认的转换方式
		data,		// 输入的字节数据
		-1,			// 输入的字符串大小 -1就是找\0结束
		nullptr,	// 输出
		0// 输出的空间大小
	);
	if (len <= 0)
		return re;
	wstring udata;
	udata.resize(len);
	// 转换为unidoce了
	MultiByteToWideChar(CP_ACP, 0, data, -1, (wchar_t*)udata.data(), len);

	// 2. unicode转UTF-8
	// 2.1 统计转换后的字节数
	len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)udata.data(), -1, nullptr, 0,
		nullptr,	// 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的
		0		// 是否使用默认替代
	);
	if (len <= 0)
		return re;
	re.resize(len);
	WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)udata.data(), -1, (char*)re.data(), len, nullptr, 0);
#else
	// linux部分
	re.resize(1024);
	int inLen = strlen(data);
	Convert((char*)"gbk", (char*)"utf-8", (char*)data, inLen, (char*)re.data(), re.size());
	int outLen = strlen(re.data());
	re.resize(outLen);
#endif
	return re;
}

int main()
{
	// 为了保证下面的测试正确,先确保我们的代码是GBK或者ANSI的
	std::cout << "Hello World 测试!\n";

	// 1、测试UTF-8转GBK
	//string gbk = UTF8ToGBK(u8"测试UTF-8转GBK");
	//cout << "gbk = " << gbk << endl;
	//cout << GBKToUTF8(gbk.c_str()) << endl;

	string ascii_string = "测试UTF-8转GBK";
	string utf8_string = GBKToUTF8(ascii_string.c_str());
	cout << "输入 utf = " << utf8_string << endl;
	string gbk = UTF8ToGBK(utf8_string.c_str());
	cout << "gbk = " << gbk << endl;
	cout << GBKToUTF8(gbk.c_str()) << endl;

	// 2、测试GBK到UTF-8的转换
	//string utf8 = GBKToUTF8("测试GBK转UTF-8再转为GBK");
	//cout << "luan ma : utf8 = " << utf8 << endl;
	//cout << "GBK = " << UTF8ToGBK(utf8.c_str()) << endl;

	system("pause");

	return 0;
}

ZPMysql库添加字符集转换函数并测试GBK

我们这边字符集转换主要涉及两方面的内容:插入和读取。

插入utf-8的数据

C++Mysql8.0数据库跨平台编程实战(下)_第16张图片

我们把代码在linux运行看看
// ZPMysql makefile

testZPMysql:test_ZPMysql.cpp libZPMysql.so
	g++ test_ZPMysql.cpp -L./ -lZPMysql -o $@ -g
libZPMysql.so:ZPData.cpp ZPMysql.cpp ZPData.h ZPMysql.h
	g++ -fPIC -shared -g $^ -I/usr/include/mysql/ -lmysqlclient -o $@
run:
	./testZPMysql
install:
	@cp libZPMysql.so /usr/lib/
	@echo "install libZPMysql.so success!"
uninstall:
	@rm -rf /usr/lib/libZPMysql.so
	@echo "uninstall libZPMysql.so success!"
clean:clear
	@echo "clear project success!"
clear:
	@rm -rf *.o *.so testZPMysql
	@echo "clear project success!"

上述makefile执行输出:
g++ -fPIC -shared ZPData.cpp ZPMysql.cpp ZPMysql.h ZPData.h -o libZPMysql.so -I/usr/include/mysql -lmysqlclient
g++ ./test_ZPMysql/test_ZPMysql.cpp -o test_ZPMysql -I./ -lZPMysql -L./
cp libZPMysql.so /usr/lib
install libZPMysql.so success!
./test_ZPMysql

在linux当中我们也测试成功,这样的话,我们同一份代码在两个系统都已经插入成功。

插入gbk的数据

utf8在各个平台通用,但是gbk数据在linux当中就没有了(无法正常显示);
这里我们先插入,等有问题了再来解决。
C++Mysql8.0数据库跨平台编程实战(下)_第17张图片

在linux当中我们测试发现是乱码,因为linux这边是当做utf-8的,要插入的字段name是gbk的,你把utf-8插进去肯定是乱码;
所以在linux当中我们要做个转换,把utf-8转换成gbk。

C++Mysql8.0数据库跨平台编程实战(下)_第18张图片

在ZPData.cpp中,把前面我们写的UTF8ToGBK和GBKToUTF8这两个函数的代码粘贴过来:
C++Mysql8.0数据库跨平台编程实战(下)_第19张图片

C++Mysql8.0数据库跨平台编程实战(下)_第20张图片

C++Mysql8.0数据库跨平台编程实战(下)_第21张图片

我们查询数据库发现显示没问题。

我们来编写从数据库中获取数据的代码:
C++Mysql8.0数据库跨平台编程实战(下)_第22张图片

在linux当中因为name字段存的是gbk的,所以获取后不能正常显示。

我们修改代码,可以判断是否是在linux当中,如果在linux当中我们要做个转换才能正常输出:
C++Mysql8.0数据库跨平台编程实战(下)_第23张图片

这样在windows和linux当中测试都没有问题。

C++Mysql8.0数据库跨平台编程实战(下)_第24张图片

我们可以看到在控制台中的utf8输出是乱码,这是因为控制台的编码是GBK的,所以我们在windows当中的时候需要转换,而在linux当中就不需要转换了,因为linux是直接支持utf-8显示的。
C++Mysql8.0数据库跨平台编程实战(下)_第25张图片

C++Mysql8.0数据库跨平台编程实战(下)_第26张图片

这样我们就完成了对于中文字符的UTF-8和GBK之间的互相转换,插入和读取功能就做完了。

简易获取数据的接口GetResult实现

C++Mysql8.0数据库跨平台编程实战(下)_第27张图片

C++Mysql8.0数据库跨平台编程实战(下)_第28张图片

C++Mysql8.0数据库跨平台编程实战(下)_第29张图片

C++Mysql8.0数据库跨平台编程实战(下)_第30张图片

C++Mysql8.0数据库跨平台编程实战(下)_第31张图片

经测试我们发现没有输出BLOG,说明我们FetchRow的时候没有把类型保存起来(为了性能的话可以在StoreResult里面保存)。

C++Mysql8.0数据库跨平台编程实战(下)_第32张图片

C++Mysql8.0数据库跨平台编程实战(下)_第33张图片

mysql的表锁和行锁代码示例购票竞争

mysql的锁一般只是针对我们做事务的时候才会用到,最常见的用法,比方说订票,就是你不能保证你这次操作是原子操作,因为订票是两个操作的,第一步先查这个票有没有售完,第二步把这张票标识为已售完。

C++Mysql8.0数据库跨平台编程实战(下)_第34张图片

行锁,我们用事务来演示,开两个线程来演示,或者让我们程序运行两遍。


    // 订票模拟(事务) t_tickets(id int, sold int)
    sql = "CREATE TABLE IF NOT EXISTS `t_tickets` \
        (`id` INT AUTO_INCREMENT, \
        `sold` INT, \
        PRIMARY KEY(`id`))";
    my.Query(sql.c_str());
    // 这里不能清空数据(因为我们模拟行锁,要运行两遍程序,两个程序共享这张表)
    //my.Query("TRUNCATE `t_tickets`");

    {
        XDATA data;
        data["sold"] = "0";     // 0是未售出
        my.Insert(data, "t_tickets");   // id=1
    
	// 取这张票,并且把这张票的标识添加进来
		my.StartTransaction();
		// 我们先看不锁的情况
		XROWS rows = my.GetResult("select * from t_tickets where sold=0 order by id");
        string id = rows[0][0].data;
		cout << "Buy ticket id is " << id << endl;

        // 模拟冲突(10秒钟之后我们更新这张票)
        this_thread::sleep_for(10s);
        data["sold"] = "1";
        string where = "where id = ";
        where += id;
        cout << my.Update(data, "t_tickets", where) << endl;
        cout << "Buy ticket id " << id << "success!" << endl;
		//my.GetResult("select * from t_tickets where sold=0 for updata");
		my.Commit();
		my.StopTransaction();

    }

    cout << endl;
    // 清理资源
    my.Close();
    cout << "test_ZPMysql!" << endl;

    getchar();

C++Mysql8.0数据库跨平台编程实战(下)_第35张图片

我们可以看到,两个人买同一张票都成功了!这就很有问题了,而且第二个人执行Update修改票的状态的时候是失败的(Update输出是0)。

C++Mysql8.0数据库跨平台编程实战(下)_第36张图片

C++Mysql8.0数据库跨平台编程实战(下)_第37张图片

我们可以看到第二个人阻塞在那里,就在那里等着,无法查询。

C++Mysql8.0数据库跨平台编程实战(下)_第38张图片

第一个人购买2号票成功后,第二个人查询的票号是3,第二个人购票成功。

这样的话就使得两者不冲突(判断资源和申请资源不冲突),我们就做了这样一个行锁,锁住这张票,

第七章 日志审计系统项目实战和课程总结

日志审计系统项目模块分析

C++Mysql8.0数据库跨平台编程实战(下)_第39张图片

C++Mysql8.0数据库跨平台编程实战(下)_第40张图片

C++Mysql8.0数据库跨平台编程实战(下)_第41张图片

C++Mysql8.0数据库跨平台编程实战(下)_第42张图片

日志审计系统Agent模块项目

// agent makefile

agent:agent.cpp XAgent.cpp XAgent.h
	g++ $^ -o $@ -I ../ZPMysql -lZPMysql
	./$@ 127.0.0.1
// ZPMysql makefile

all:libZPMysql.so test_ZPMysql install
	#./test_ZPMysql
libZPMysql.so:ZPData.cpp ZPMysql.cpp ZPData.h ZPMysql.h
	g++ -fPIC -shared $^ -o $@ -I/usr/include/mysql/ -lmysqlclient 
test_ZPMysql:../test_ZPMysql/test_ZPMysql.cpp
	g++ $^ -o $@ -I./ -lZPMysql -L./
install:
	cp libZPMysql.so /usr/lib/
	@echo "install libZPMysql.so success!"
uninstall:
	rm -rf /usr/lib/libZPMysql.so
	@echo "uninstall libZPMysql.so success!"
clean:
	rm -rf *.o *.so test_ZPMysql
	rm -rf /usr/lib/libZPMysql.so
	@echo "clear project success!"

日志审计系统Center模块项目

Center模块安装配置和数据初始化

// center makefile

center:center.cpp XCenter.cpp XCenter.h
	g++ $^ -o $@ -I ../ZPMysql -lZPMysql
	./$@ install 127.0.0.1
clean:
	rm -rf *.o
	rm -rf center

在linux下用makefile的时候,如果你的程序某个函数返回-1的话,make就会认为你的程序出错了,makefile可能会停止,所以我们这里测试的时候还是返回0吧。

Center审计策略表安装和策略添加

C++Mysql8.0数据库跨平台编程实战(下)_第43张图片

完成Center的添加设备

// center makefile

center:center.cpp XCenter.cpp XCenter.h
	g++ $^ -o $@ -I ../ZPMysql -lZPMysql
	./$@ install 127.0.0.1
	./$@ add 192.168.0.202 fileserver1
	./$@ add 192.168.0.201 fileserver2
clean:
	rm -rf *.o
	rm -rf center

Center主循环获取到Agent发送的最新事件

// center makefile

center:center.cpp XCenter.cpp XCenter.h
	g++ $^ -o $@ -I ../ZPMysql -lZPMysql
	#./$@ install 127.0.0.1
	#./$@ add 192.168.0.202 fileserver1
	#./$@ add 192.168.0.201 fileserver2
	./$@
clean:
	rm -rf *.o
	rm -rf center

你可能感兴趣的:(windows程序设计,数据库,c++,mysql)