C++实现简易连接池(详细版)上

/***********************************************ConnectionPool*****************************************/

hello~大大大家好!本次主包为大家带来的是连接池学习贴,接下来我们对连接池这个小项目做一些基本的介绍,对了!关于内存池有没有兴趣呢,有的话点开此链接就可以跳转哟~话不多说,正片开始!https://mpbeta.youkuaiyun.com/mp_blog/creation/editor/148906710

一、技术点介绍

本项目不大但是可以为你带来满满干货。本项目主要涉及了MYSQL编程、单例模式设计、queue队列容器、C++11编程、CAS原子特性、生产者消费者模型、线程的同步与互斥、智能指针shared_ptr。(这些是不是看着很多,就像HR看你的简历一样……)。

二、项目背景

为了提高访问数据库的效率,我们引入了连接池。那么为什么它就可以提高访问效率呢?其实,在并发程度或者说访问量不大的场景下,连接池是发挥不了优势的。在访问数据库的过程中,客户端需要发起TCP三次握手------MYSQL验证---------响应操作--------MYSQL释放回收资源--------TCP四次挥手。这一系列过程似乎花不了多长时间,但是在高并发大量的访问场景下,频繁地TCP三次握手四次挥手、连接的验证以及回收是一种极大的浪费且似乎没有必要。那么连接池就应运而生。

服务器大量响应belike::::::::::::::::::::::::::::::::

三、连接池功能点

本项目主要实现连接池的基本功能。包括初始连接量、最大连接量、最大空闲时间和连接超时时间。

连接池运行过程

在没有引入连接池的时候,客户端发起使用mysql进行数据库的访问,其实是客户端与mysql客户端进行连接,再通过MySQL客户端与MySQL服务器进行连接,而客户对数据库的操作实质上是MySQL服务器对数据库的操作。BUT,MySQL客户端与MySQL服务器的连接包含TCP的连接与释放、MySQL之间的验证与回收,占用不少时间。

引入连接池以后,我们预先开辟一些数量的连接类,当客户端需要操作数据库的时候,MySQL客户端就从连接池取一个连接进行与MySQL服务器的连接,跳过了tcp连接与释放、MySQL的验证与回收(其实这些工作只是在初始化连接池操作了一次)。当使用完连接后再将连接放归连接池。

连接池设计功能点

1、连接池只需要一个实例,因此是单例设计模式(因为在客户端申请连接的过程中连接池是从始至终一个就够了
连接需要很多个
2、从连接池中可以获取与MySQL的连接connection
3、空闲连接connection全部放在队列中保存,因为该队列是共享资源,需要进行线程安全
4、如果初始的连接connection用完了,需要动态扩充连接,最大为maxsize
5、队列中超过最大空闲时间的连接就释放掉,最少保留初始数量的连接就足够了
6、如果队列中无可用连接,且超过连接超时时间,那么就判定连接失败
7、用户获取的连接用智能指针来管理,因为智能指针可用做到线程安全且自动回收
8、连接的生产和消费其实是生产者消费者模型

四、开始实现

gogogo~出发喽~

我们开始实现具体类的代码。

代码结构

头文件别忘了

#pragma once
#include <string>
#include <queue>
#include "Connection.h"
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <memory>
using namespace std;

connectionPool

//单例模式实现
class ConnectionPool {
public:
	static ConnectionPool* getConnectionPool();
	//用户接口,获得连接来与数据库进行交互
	shared_ptr<Connection> getConnection();
private:
	bool loadConfig();//加载配置文件(包括ip,端口号,MySQL密码等等)
	void produceconnections();//产生连接
	void cleanconnections();//清理空闲连接
	ConnectionPool();//构造函数
	
	
	//内部属性
	string _ip;//ip地址
	unsigned int _port;//端口号
	string _username;//MySQL用户名
	string _password;//MySQL密码
	string _dbname;//数据库名字
	int _initSize;//初始连接池连接数量
	int _maxSize;//最大连接池连接数量
	int _maxIdleTime;//最大空闲时间
	int _ConnectionTimeOut;//超时连接时间

	queue<Connection*> _pool;//连接池(队列存储)
	mutex _queueMutex;//互斥锁,保证线程安全
	atomic_int _currentSize;//原子型变量,记录当前连接总数,也是保证线程安全
	condition_variable _Empty;//同步信号量

};

上面各个变量已经解释的很清楚了,不用我再在外面解释了吧。如果需要记得评论一下……

配置文件(mysqlConfg.ini)

#配置文件
ip=127.0.0.1
port=3306
#这里注意修改为你的用户名
username=root
#这里注意修改为你的密码
password=200319
#这里注意修改为你的数据库名
dbname=chat

initSize=10
maxSize=1024
#单位秒
maxIdleTime=60
#单位毫秒
connectionTimeout=100

我是使用vs2022平台书写的代码,配置文件就命名为mysqConfg.ini即可

日志函数

#pragma once
#include <iostream>
#define LOG(str)\
    cout<<__FILE__<<" : "<<__LINE__<<" : "<<\
    __TIMESTAMP__<<" : "<<str<<endl;

加载配置文件函数


bool ConnectionPool::loadConfig() {
	FILE* fp = fopen("mysqlConfg.ini", "r");
	if (fp == NULL) {
		LOG("mysqlConfg.ini not found");
		return false;
	}
	while (!feof(fp)) {//如果不是文件末尾就循环执行
		char line[1024] = { 0 };//写缓冲区
		fgets(line, 1024, fp);
		string str = line;
		int idx = str.find("=",0);//找“=”的索引,找不到说明不是配置信息就接着找
		if (idx == -1) {
			continue;
		}
		int enidx = str.find("\n", idx);
		string key = str.substr(0, idx);//配置文件前面的配置信息名字
		string value = str.substr(idx + 1, enidx - idx - 1);//配置信息对应的值
        //下面是分情况讨论,得到的究竟是何方神圣
		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;
	}

}

连接池构造函数

ConnectionPool::ConnectionPool() {
	if (!loadConfig()) {
		LOG("load mysqlConfg.ini failed");
		return;
	}
	//创建初始连接
	for (int i = 0; i < _initSize; i++) {
		Connection* conn = new Connection();
		conn->connect(_ip, _port, _username, _password, _dbname);
		_pool.push(conn);
		conn->resetFreeTime();//重置空闲时间(入队列就得重置)
		_currentSize++;
	}
	//启动生产CONN线程
	thread producer(bind(&ConnectionPool::produceconnections, this));
	producer.detach();
	//启动清理线程,将超过最大空闲时间的连接清理掉
	thread cleaner(bind(&ConnectionPool::cleanconnections, this));
	cleaner.detach();


}

绑定器将成员函数和当前对象绑定,要不然这个成员方法就不能用在这里。

/*************************************************continue*************************************************/

下集预告:

实现生产者消费者线程函数

设计连接类

实现回收创建连接

……

ps:写太多了,剩下大约一半留到下一篇分解。up扛不住了,打会最后生还者去了,记住最后生还者只有第一部,没有第二部!大家加油消化,我先撤了,bye~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值