【TinyWebServer源码解析】(六)数据库连接池

本文是对github上万star项目源码解析,供大家学习交流
项目地址:
https://github.com/qinguoyi/TinyWebServer

六、 数据库连接池

校验 & 数据库连接池

===============

数据库连接池

​ =>单例模式,保证唯一

​ =>list实现连接池

​ =>连接池为静态大小

​ =>互斥锁实现线程安全

校验

​ =>HTTP请求采用POST方式

​ =>登录用户名和密码校验

​ =>用户注册及多线程注册安全

===============

共两个个class,分别是connection_pool和connectionRAII

===============

头文件

#include <stdio.h>
#include <list>
#include <mysql/mysql.h>
#include <error.h>
#include <string.h>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <pthread.h>

#include "../lock/locker.h"
#include "../log/log.h"
1、connectionRAII类
  • RAII(Resource Acquisition Is Initialization)即资源获取即初始化,是一种C++编程技巧,用于管理资源。connectionRAII类依托于connection_pool类,用于在函数执行期间自动申请连接和自动释放连接,避免了手动申请和释放资源出现的错误和异常情况;
  • 在创建connectionRAII对象时,它会自动调用connection_pool的GetConnection()方法来获取数据库连接;
  • 在connectionRAII对象生命周期结束时,它会自动调用connection_pool的ReleaseConnection()方法来释放数据库连接。
1.1 类的定义
class connectionRAII{
public:
	connectionRAII(MYSQL **con, connection_pool *connPool);
	~connectionRAII();
	
private:
	MYSQL *conRAII;
	connection_pool *poolRAII;
};
1.2 构造函数与析构函数

构造函数,用于管理MySQL数据库连接的,采用资源获取即初始化(RAII)的方式进行自动化管理

在创建connectionRAII对象时,会自动获取一个数据库连接,并将其保存在conRAII中,同时也保存了连接池的指针,方便之后的释放操作

// 两个参数分别是:MYSQL类型的指针SQL和connection_pool类型的指针connPool
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){
	*SQL = connPool->GetConnection(); // 将从连接池中获取到的一个数据库连接赋值给SQL指向的内存空间
	
	conRAII = *SQL; // 将SQL所指向的内存空间的内容赋值给了conRAII成员变量
	poolRAII = connPool; // 将connPool所指向的内存空间的内容赋值给了poolRAII成员变量
}

析构函数,用于释放数据库连接资源。在connectionRAII对象被销毁时(例如,作用域结束或delete运算符调用),会自动调用该析构函数

在connectionRAII对象被销毁时,会自动释放其所持有的数据库连接,避免了内存泄漏等问题。同时,由于使用了connection pool,还可以提高程序的效率,避免频繁创建和销毁数据库连接带来的开销

connectionRAII::~connectionRAII(){
	poolRAII->ReleaseConnection(conRAII);
}
2、connection_pool类
  • 维护一个MySQL连接池,提供获取和释放连接的方法;
  • 可以初始化连接池参数,包括URL、用户名、密码、数据库名、端口号、最大连接数等;
  • 实现了单例模式,确保只有一个连接池实例存在;
  • 通过使用锁和信号量控制并发访问连接池的线程安全问题。
2.1 类的定义
class connection_pool
{
public:
	MYSQL *GetConnection();				 //获取数据库连接
	bool ReleaseConnection(MYSQL *conn); //释放连接
	int GetFreeConn();					 //获取连接
	void DestroyPool();					 //销毁所有连接

	//单例模式
	static connection_pool *GetInstance();

	void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); 

private:
	connection_pool();
	~connection_pool();

	int m_MaxConn;  //最大连接数
	int m_CurConn;  //当前已使用的连接数
	int m_FreeConn; //当前空闲的连接数
	locker lock;
	list<MYSQL *> connList; //连接池
	sem reserve;

public:
	string m_url;			 //主机地址
	string m_Port;		 //数据库端口号
	string m_User;		 //登陆数据库用户名
	string m_PassWord;	 //登陆数据库密码
	string m_DatabaseName; //使用数据库名
	int m_close_log;	//日志开关
};
2.2 构造函数与析构函数

构造函数,成员变量m_CurConnm_FreeConn被初始化为0

connection_pool::connection_pool(){
	m_CurConn = 0; //当前已使用的连接数
	m_FreeConn = 0; //当前空闲的连接数
}

析构函数,对连接池进行释放

connection_pool::~connection_pool(){
	DestroyPool();
}
2.3 GetInstance()方法

在整个应用程序中,只需要调用GetInstance()方法获取该连接池的唯一实例,然后就可以使用该实例执行数据库操作

connection_pool *connection_pool::GetInstance(){
    // static确保整个应用程序中只有一个数据库连接池
	static connection_pool connPool;
	return &connPool;
}
2.4 init()方法

用于初始化连接池

//构造初始化
void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log){
    // 将传入参数赋值给成员变量
	m_url = url;
	m_Port = Port;
	m_User = User;
	m_PassWord = PassWord;
	m_DatabaseName = DBName;
	m_close_log = close_log;
	// 创建了指定数量的数据库连接,并将连接添加到connList列表中
	for (int i = 0; i < MaxConn; i++){
		MYSQL *con = NULL;
		con = mysql_init(con);
		// 如果连接创建失败,则会输出错误信息并立即退出程序
		if (con == NULL){
			LOG_ERROR("MySQL Error");
			exit(1);
		}
        // 创建mysql连接
		con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
		// 如果连接创建失败,则会输出错误信息并立即退出程序
		if (con == NULL){
			LOG_ERROR("MySQL Error");
			exit(1);
		}
        // 如果成功创建连接,则将连接添加到connList列表中
		connList.push_back(con);
        // 并将m_FreeConn计数器加1,表示新增一个可用连接
		++m_FreeConn;
	}
	// 创建了一个名为reserve的信号量,初始值为m_FreeConn,表示当前空闲连接数
	reserve = sem(m_FreeConn);
	// m_MaxConn变量被设置为m_FreeConn,表示最大连接数也是当前可用连接数
	m_MaxConn = m_FreeConn;
}

在整个程序生命周期中,只需要调用一次init()函数,就可以初始化连接池,然后在需要访问数据库时,从连接池中获取连接,执行完操作后,将连接归还到连接池中以供下次使用

2.5 GetConnection()方法

数据库连接池中获取可用连接的函数。当有请求时,它会从数据库连接池(connList)中返回一个可用连接(con),并更新使用和空闲连接数

//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL *connection_pool::GetConnection(){
	MYSQL *con = NULL;
	// 检查连接池中是否有可用连接,如果没有则返回NULL表示没有可用连接
	if (0 == connList.size())
		return NULL;
	// 在没有可用连接的情况下会将线程阻塞以等待可用连接
	reserve.wait();
	
	lock.lock();
	// 从连接池的头部取出一个连接
	con = connList.front();
    // 同时从连接池中移除该连接
	connList.pop_front();
	
    // 更新连接池中的空闲和正在使用的连接数
	--m_FreeConn;
	++m_CurConn;

	lock.unlock();
    // 返回获取到的可用连接
	return con;
}
2.6 ReleaseConnection()方法

数据库连接池中释放连接的函数。当使用完一个连接后,可以通过调用该函数将该连接释放回连接池,并更新使用和空闲连接数

//释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con){
    // 检查传入的参数con是否为空指针,如果是则直接返回false表示释放失败
	if (NULL == con)
		return false;

	lock.lock();
	// 将连接con添加到连接池的尾部
	connList.push_back(con);
    // 更新连接池中的空闲和正在使用的连接数
	++m_FreeConn;
	--m_CurConn;

	lock.unlock();
	// 调用reserve.post()方法,这个方法会通知之前被阻塞的线程,告诉它们当前有可用连接了,以便它们能够从连接池中获取连接
	reserve.post();
	return true;
}
2.7 GetFreeConn()方法

数据库连接池中获取当前空闲连接数的函数。它直接返回连接池对象中的m_FreeConn成员变量,该变量记录了当前连接池中的空闲连接数量

//当前空闲的连接数
int connection_pool::GetFreeConn(){
	return this->m_FreeConn;
}
2.8 DestroyPool()方法

数据库连接池中销毁连接池的函数。当不再需要使用连接池时,可以通过调用该函数来销毁连接池,并释放已分配的资源

//销毁数据库连接池
void connection_pool::DestroyPool(){
	lock.lock();
    // 检查连接池中是否还有连接
	if (connList.size() > 0){
		list<MYSQL *>::iterator it;
        // 遍历所有连接并调用mysql_close()方法关闭连接,释放资源
		for (it = connList.begin(); it != connList.end(); ++it){
			MYSQL *con = *it;
			mysql_close(con);
		}
        // 将连接池中的m_CurConn和m_FreeConn成员变量都置为0,表示当前没有任何连接在使用或者空闲
		m_CurConn = 0;
		m_FreeConn = 0;
        // 调用connList.clear()方法清空连接池中的连接列表
		connList.clear();
	}
	lock.unlock();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值