C++实现数据库连接池项目

这个项目介绍了用C++语言实现MySQL数据库的连接池项目,包括项目功能点分析,功能设计,项目编码以及压力测试四个部分。项目代码量约400行,涉及了11个核心功能点,包括 MySQL数据库编程、单例模式、queue队列容器、C++11多线程编程、线程互斥、线程同步通信、unique_lock智能锁、基于CAS的原子整形、智能指针shared_ptr、lambda表达式、生产者-消费者线程模型。

要求能够熟练C++语言、设计模式、智能指针和多线程编程、线程互斥、同步通信、生产者-消费者线程模型的综合应用,能提高知识的综合实践扩展能力。

编译环境:window10 Visual Studio 2019,需要加入mysql环境,MySQL版本为5.6

有任何问题请在下面评论,谢谢。

接下来直接上代码:

文件结构如下图:
项目结构

头文件代码如下:

1.CommonConnectionPool.h

#pragma once
#include<queue>
#include<mutex>
#include<string>
#include"Connection.h"
#include"public.h"
#include <iostream>
#include<atomic>
#include<thread>
#include<functional>
#include<ctime>
#include<memory>
#include<condition_variable>
using namespace std;
/*实现连接池功能模块*/

class ConnectionPool
{
public:
	static ConnectionPool* GetConnectionPool();
	shared_ptr<Connection> getConnection();
private:
	ConnectionPool();
	ConnectionPool(ConnectionPool&) = delete;
	ConnectionPool& operator=(ConnectionPool&) = delete;
	bool loadConfigFile();//加载数据库信息
	void produceConnectionTask();//生产连接
	void scannerConnectionTask();//扫描空闲连接并将其delete

	string _ip;//数据库IP地址
	unsigned short _port;//数据库端口,3306
	string username;//连接数据的用户名
	string password;//用户名的密码
	string _dbname;//数据库名
	int _initSize;//连接池的初始大小
	int _maxSize;//连接池的最大连接数
	int _maxIdleTime;//连接池的最大空闲时间
	int _connectionTimeout;//获取数据库超时时间

	//保存数据库连接的容器
	queue<Connection*> _connectionQue;
	//保证容器线程安全的互斥锁
	mutex _queueMutex;
	//设置条件变量,用于生产者与消费者的线程安全控制
	condition_variable cv;
	//连接池中现有连接的数量
	atomic_int _connectionCnt;
};

2.Connection.h

#pragma once
#include <winsock.h>
#include <mysql.h>
#include <string>
#include <ctime>
using namespace std;

/*
实现MySQL数据库的操作
*/
class Connection
{
public:
	// 初始化数据库连接
	Connection();
	// 释放数据库连接资源
	~Connection();
	// 连接数据库
	bool connect(string ip,unsigned short port,string user,string password,string dbname);
	// 更新操作 insert、delete、update
	bool update(string sql);
	// 查询操作 select
	MYSQL_RES* query(string sql);

	//刷新数据库的存活时间
	void setAliveTime() { _alivetime = clock(); }
	clock_t getAliveTime() { return _alivetime - clock(); }
private:
	MYSQL* _conn; // 表示和MySQL Server的一条连接
	clock_t _alivetime;
};

3.pch.h

// 入门提示: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

#ifndef PCH_H
#define PCH_H

// TODO: 添加要在此处预编译的标头

#endif //PCH_H


4.public.h

#pragma once

#define LOG(str) cout << __FILE__ << ":" << __LINE__<<" "<<__TIMESTAMP__<<" : "<< str <<endl;

源文件

1.CommonConnectionPool.cpp

#include"CommonConnectionPool.h"

//创建连接池
ConnectionPool* ConnectionPool::GetConnectionPool()
{
	static ConnectionPool pool;
	return &pool;
}

//创建初始线程
ConnectionPool::ConnectionPool()
{
	if (!loadConfigFile())
	{
		return;
	}
	//创初始连接池
	for (int i = 0; i < _initSize; ++i)
	{
		Connection* p = new Connection();
		p->connect(_ip,_port,username,password,_dbname);
		p->setAliveTime();
		_connectionQue.push(p);
		_connectionCnt++;
	}
	//创建一个线程,负责产生连接
	thread produce(bind(&ConnectionPool::produceConnectionTask,this));
	produce.detach();
	//创建一个线程,对连接池中的元素进行扫描,空闲时间太大的delete
	thread scanner(bind(&ConnectionPool::scannerConnectionTask, this));
	scanner.detach();
}

//加载数据库配置
bool ConnectionPool::loadConfigFile()
{
	FILE *pf = fopen("mysql.ini","r");
	if (pf == nullptr)
	{
		LOG("mysql.ini file is not exist!");
		return false;
	}
	while (!feof(pf))
	{
		char line[1024] = { 0 };
		fgets(line , 1024, pf);
		string str = string(line);
		int idx = str.find("=",0);
		if (idx == -1)
		{
			continue;
		}
		int endidx = str.find("\n", idx);
		string key = str.substr(0, idx);
		string value = str.substr(idx + 1, endidx - idx - 1);
		//cout << key << ":" << value << endl;
		if (key == "ip")
		{
			_ip = value;
		} else if(key == "port")
		{
			_port = atoi(value.c_str());
		}
		else if (key == "username")
		{
			username = value;
		}
		else if (key == "password")
		{
			password = value;
		}
		else if (key == "dbname")
		{
			_dbname = value;
		}
		else if (key == "initSize")
		{
			_initSize = atoi(value.c_str());
		}
		else if (key == "maxSize")
		{
			_maxSize = atoi(value.c_str());
		}
		else if (key == "maxIdleTime")
		{
			_maxIdleTime = atoi(value.c_str());
		}
		else if (key == "connectionTimeout")
		{
			_connectionTimeout = atoi(value.c_str());
		}
	}
	return true;
}

//生产连接
void ConnectionPool::produceConnectionTask()
{
	while (1)
	{
		unique_lock<mutex> lck(_queueMutex);
		//如果连接池里还有元素,则等待,不产生元素直到连接池为空
		while (!_connectionQue.empty())
		{
			cv.wait(lck);
		}
		//产生元素并判断元素是否大于连接池最大连接数
		if (_connectionCnt < _maxSize)
		{
			Connection* cp = new Connection();
			cp->connect(_ip, _port, username, password, _dbname);
			cp->setAliveTime();
			_connectionQue.push(cp);
			_connectionCnt++;
		}
		//产生元素成功,通知消费者消费
		cv.notify_all();
	}
	
}

//获取连接
shared_ptr<Connection> ConnectionPool::getConnection()
{
	unique_lock<mutex> lock(_queueMutex);
	while (_connectionQue.empty())
	{
		//判断是否是因连接超时而导致程序被唤醒
		if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout)))
		{
			if (_connectionQue.empty())
			{
				LOG("数据库连接超时!");
				return nullptr;
			}
		}
	}
	//从连接池中拿一个元素出来,并重新定义其析构,当该智能指针被释放时应放入连接池中而不是delete
	shared_ptr <Connection> cp(_connectionQue.front(), [&](Connection* conn) {unique_lock<mutex> lock(_queueMutex); conn->setAliveTime(); _connectionQue.push(conn); });
	_connectionQue.pop();
	cv.notify_all();
	return cp;
}
//扫描空闲连接并将其delete
void ConnectionPool::scannerConnectionTask()
{
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(_maxSize));
		unique_lock<mutex> lock(_queueMutex);
		while (_connectionCnt > _initSize)
		{
			Connection* p = _connectionQue.front();

			if (p->getAliveTime() >=(_maxIdleTime*1000))
			{
				_connectionQue.pop();
				_connectionCnt--;
				delete p;
			}
		}
	}
	
}

2.Connection.cpp

#pragma once
#include "pch.h"
#include "public.h"
#include "Connection.h"
#include <iostream>
using namespace std;

Connection::Connection()
{
	// 初始化数据库连接
	_conn = mysql_init(nullptr);
}

Connection::~Connection()
{
	// 释放数据库连接资源
	if (_conn != nullptr)
		mysql_close(_conn);
}

bool Connection::connect(string ip, unsigned short port,string username, string password, string dbname)
{
	// 连接数据库
	MYSQL* p = mysql_real_connect(_conn, ip.c_str(), username.c_str(),
		password.c_str(), dbname.c_str(), port, nullptr, 0);
	return p != nullptr;
}

bool Connection::update(string sql)
{
	// 更新操作 insert、delete、update
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("更新失败:" + sql);
		return false;
	}
	return true;
}

MYSQL_RES* Connection::query(string sql)
{
	// 查询操作 select
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("查询失败:" + sql);
		return nullptr;
	}
	return mysql_use_result(_conn);
}

3.mian.cpp

使用多线程和单线程分别使用连接池做测试

#include "pch.h"
#include <iostream>
using namespace std;
#include "Connection.h"
#include "CommonConnectionPool.h"

int main()
{
	//Connection conn;
	//conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
	clock_t begin = clock();
	thread t1([]() {
		for (int i = 0; i < 2500; i++)
		{
			/*Connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn.update(sql);*/
			ConnectionPool* cp = ConnectionPool::GetConnectionPool();
			
			shared_ptr<Connection> conn = cp->getConnection();
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			//conn->connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn->update(sql);
			
		}
		});
	thread t2([]() {
		for (int i = 0; i < 2500; i++)
		{
			/*Connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn.update(sql);*/
			ConnectionPool* cp = ConnectionPool::GetConnectionPool();
			
			shared_ptr<Connection> conn = cp->getConnection();
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			//conn->connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn->update(sql);
			
		}
});
	thread t3([]() {
		for (int i = 0; i < 2500; i++)
		{
			/*Connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn.update(sql);*/
			ConnectionPool* cp = ConnectionPool::GetConnectionPool();
			
			shared_ptr<Connection> conn = cp->getConnection();
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			//conn->connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn->update(sql);
			
		}
		});

	thread t4([]() {
		for (int i = 0; i < 2500; i++)
		{
			/*Connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn.update(sql);*/
			ConnectionPool* cp = ConnectionPool::GetConnectionPool();
			
			shared_ptr<Connection> conn = cp->getConnection();
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
			//conn->connect("127.0.0.1", 3306, "root", "123456", "chat");
			conn->update(sql);
			
		}
		});

	t1.join();
	t2.join();
	t3.join();
	t4.join();
	clock_t end = clock();
	cout << end - begin << "ms" << endl;
#if 0
	ConnectionPool* cp = ConnectionPool::GetConnectionPool();
	clock_t begin = clock();
	for (int i = 0; i < 10000; i++)
	{
		shared_ptr<Connection> conn = cp->getConnection();
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhouli", 21, "male");
		//conn->connect("127.0.0.1", 3306, "root", "123456", "chat");
		conn->update(sql);
	}
	clock_t end = clock();
	cout << end - begin<<"ms" << endl;
	
	
	//cp->loadConfigFile();

	Connection conn;
	char sql[1024] = { 0 };
	sprintf(sql,"insert into user(name,age,sex) values('%s',%d,'%s')","zhouli",21,"male");
	conn.connect("127.0.0.1",3306,"root","123456","chat");
	conn.update(sql);
	return 0;
#endif
}

4 mysql.ini

#保存数据库的信息

ip=127.0.0.1
port=3306
username=root
password=123456
dbname=chat
initSize=10
maxSize=1024
maxIdleTime=60
connectionTimeout=100
数据库连接池是一种常见的优化数据库性能的方法,可以提高应用程序对数据库的访问效率和性能。以下是实现数据库连接池的一些步骤: 1. 初始化连接池:在应用程序启动时,创建一定数量的数据库连接,并将其保存到连接池中。 2. 确定连接池大小:根据应用程序的需求和数据库的负载情况,确定连接池的大小,即可以容纳的最大连接数。 3. 确定连接的有效性:在连接池中的连接可能会因为网络故障或数据库错误而失效。因此,需要对连接进行有效性检查,以确保连接池中只有有效的连接。 4. 管理连接的释放:在应用程序使用完连接后,需要将连接释放回连接池中,以便其他应用程序可以使用它们。同时,需要确保连接在使用后被及时关闭,以避免因连接泄漏而导致的资源浪费。 5. 处理连接池溢出:当连接池中的连接数达到最大值时,需要处理连接池溢出的情况,例如等待其他应用程序释放连接,或者创建新的连接以容纳更多的请求。 6. 定期检查连接池:需要定期检查连接池中的连接是否有效,并清除过期的连接,以避免资源浪费。 7. 实现线程安全:连接池需要实现线程安全,以避免并发访问时出现的问题,例如连接池的竞争条件和死锁问题。可以使用锁或同步机制来实现线程安全。 总之,实现数据库连接池需要考虑很多因素,包括连接的有效性、连接池大小、连接池溢出、释放连接等,需要根据具体的应用程序需求来进行配置和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值