添加数据库连接池,实现注册和登录功能
本系列文章:
添加数据库连接池,实现注册和登录功能
单例模式
本部分内容主要参考博客单例模式 - 巴基速递 | 爱编程的大丙 (subingwen.cn),博主总结的很多东西都非常好!
在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。
如果使用单例模式,首先要保证这个类的实例有且仅有一个,为了把一个类可以实例化多个对象的路堵死,可以做如下处理:
构造函数私有化,在类内部只调用一次,这个是可控的。
由于使用者在类外部不能使用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,我们都会把这个静态对象的访问权限设置为私有的。
在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。
拷贝构造函数私有化或者禁用(使用 = delete)
拷贝赋值操作符重载函数私有化或者禁用
在实现一个单例模式的类的时候,有两种处理模式:
饿汉模式
饿汉模式就是在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象。
// 饿汉模式
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
int main()
{
TaskQueue* obj = TaskQueue::getInstance();
}
懒汉模式
懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化。懒汉模式需要关注线程安全问题,在 C++11 中引入了原子变量 atomic,通过原子变量可以实现一种安全的懒汉模式的单例,不足之处就是使用这种方法实现的懒汉模式的单例执行效率低一些。因此更常用的是使用静态局部局部对象:
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
static TaskQueue taskQ;
return &taskQ;
}
void print()
{
cout << "hello, world!!!" << endl;
}
private:
TaskQueue() = default;
};
int main()
{
TaskQueue* queue = TaskQueue::getInstance();
queue->print();
return 0;
}
区别:懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。
数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
数据库连接是一种关键的有限的昂贵的资源。一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池,由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。
SqlConnPool::~SqlConnPool() {
ClosePool();
}
SqlConnPool* SqlConnPool::Instance() { // 使用静态变量的懒汉单例模式
static SqlConnPool connPool;
return &connPool;
}
// 初始化数据库连接池
void SqlConnPool::Init(const char* host, int port,
const char* user,const char* pwd, const char* dbName,
int connSize = 10) {
assert(connSize > 0);
for (int i = 0; i < connSize; i++) {
MYSQL *sql = nullptr;
sql = mysql_init(sql);
if (!sql) {
cout << "MySql init error!" << endl;
assert(sql);
}
sql = mysql_real_connect(sql, host,
user, pwd,
dbName, port, nullptr, 0);
if (!sql) {
cout << "MySql Connect error!" << endl;
}
connQue_.push(sql);
}
MAX_CONN_ = connSize;
sem_init(&semId_, 0, MAX_CONN_); // 初始化信号量
}
MYSQL* SqlConnPool::GetConn() { // 从连接池取一个连接
MYSQL *sql = nullptr;
if(connQue_.empty()){
cout << "SqlConnPool busy!" << endl;
return nullptr;
}
sem_wait(&semId_); // 等待信号量
{
lock_guard<mutex> locker(mtx_); // 操作数据库连接池需要加锁
sql = connQue_.front();
connQue_.pop();
}
return sql;
}
void SqlConnPool::FreeConn(MYSQL* sql) { // 释放一个连接
assert(sql);
lock_guard<mutex> locker(mtx_);
connQue_.push(sql);
sem_post(&semId_); // 释放信号量
}
void SqlConnPool::ClosePool() {
lock_guard<mutex> locker(mtx_);
while(!connQue_.empty()) {
auto item = connQue_.front();
connQue_.pop();
mysql_close(item);
}
mysql_library_end();
}
实现注册和登录功能,需要在解析 HTTP 请求中加入对 Post 请求的解析
// POST请求体示例:action=user_login&username=%E5%8F%91&password=+%E5%8F%91&rememberme=1
void HttpRequest::ParseFromUrlencoded_() {
if(body_.size() == 0) { return; }
string key, value;
int num = 0;
int n = body_.size();
int i = 0, j = 0;
for(; i < n; i++) {
char ch = body_[i];
switch (ch) {
case '=':
key = body_.substr(j, i - j);
j = i + 1;
break;
case '+':
body_[i] = ' ';
break;
case '%':
num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);
body_[i + 2] = num % 10 + '0';
body_[i + 1] = num / 10 + '0';
i += 2;
break;
case '&':
value = body_.substr(j, i - j);
j = i + 1;
post_[key] = value;
break;
default:
break;
}
}
assert(j <= i);
if(post_.count(key) == 0 && j < i) {
value = body_.substr(j, i - j);
post_[key] = value;
}
}