Tinywebserver数据库连接池源码分析day0

Tinywebserver的第一天

源自github:GitHub - qinguoyi/TinyWebServer: :fire: Linux下C++轻量级WebServer服务器

Tinywebserver数据库连接池源码分析day0_第1张图片

这是项目的数据库连接池一共有两个文件,.h/.cpp 

先看md文档

数据库连接池
* 单例模式,保证唯一
* list实现连接池
* 连接池为静态大小
* 互斥锁实现线程安全

直接看sql_connection_pool.cpp,在代码里看看具体是怎么写的:

/**
 * 初始化线程池
*/
connection_pool::connection_pool()
{
	//当前已使用的连接数
	m_CurConn = 0;

	//当前空闲的连接数
	m_FreeConn = 0;
}

上面这个函数就不说了,注释已经写得很清楚了

/**
 * 设置线程池使用单例模式
 * 即确保只有单个对象被创建。
*/
connection_pool *connection_pool::GetInstance()
{
	//创建静态对象
	static connection_pool connPool;
	return &connPool;
}

这个函数就是md文档中所说——连接池的单例模式,这个静态的连接池对象保证了全局唯一性,但是你或许会问这怎么能保证唯一性,如果要new该类一个对象是不是就不能保证唯一性了,接下来看这个连接池的定义:

class connection_pool
{
public:
	//单例模式
	static connection_pool *GetInstance();

    //省略一部分代码...
private:
	connection_pool();
	~connection_pool();

	int m_MaxConn;  //最大连接数
	int m_CurConn;  //当前已使用的连接数
	int m_FreeConn; //当前空闲的连接数

	list connList; //连接池
    //省略一部分代码...
}

可以看到我们将连接池定义为类的私有即可保证这里定义的静态类对象的唯一性。

    list connList; //连接池

上面这一行定义了这个一个list类型的变量,而list的底层实现是双向链表,其实就是链表的每一个节点都存的是一条mysql的连接而已,这就是所谓的线程池。 

ok,接下来看下一个函数

//构造初始化
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;

	// 使用最大连接数初始化连接池
	for (int i = 0; i < MaxConn; i++)
	{
		MYSQL *con = NULL;
		con = mysql_init(con); // 初始化MySQL连接
		
		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.push_back(con); // 将连接添加到连接池
		++m_FreeConn;// 增加可用连接数
	}

	//设置信号量为当前空闲的连接数
	reserve = sem(m_FreeConn); 

	m_MaxConn = m_FreeConn; // 设置最大连接数
}

这个函数做的事情比较简单,就是用你给的参数值来真正生成一个连接池,

1. 参数赋值给类对象;

2. 循环以获得一条新的mysql的连接,如果成功就把这条连接加入进连接池;

3. 这时候循环结束了,把你所有成功的连接数赋值给最大连接数。

ok,这个时候你就有了一个连接池,其实做的事情也比较简单对吧。

//设置信号量为当前空闲的连接数
	reserve = sem(m_FreeConn); 

这一行代码是什么意思呢,信号量这个东西呢就好比厕所的坑位,如果你不是变态的话,一个坑位同一时间只能进去一个人对吧,那么我现在在厕所门口有个显示屏上面显示剩余坑位数,进去一个人我就信号量-1,那么只要我把信号量的初值设置为总坑位的数量,是不是里面没坑位我就知道了,不用进去而是等人出来我再进去了。

class sem
{
public:
    sem(int num)
    {
        if (sem_init(&m_sem, 0, num) != 0){ 
            throw std::exception(); //如果初始化失败,抛出异常
        }
    }
    //省略一部分代码...
}

这是lock.h中的sem(信号量)类

初始化信号量对象 sem,将其设置为 value。如果 pshared 设置为非零,则将信号量共享给其他进程。

sem_init 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。

原型:int sem_init (sem_t *__sem, int __pshared, unsigned int __value) 

接下来看下一个函数,现在有了连接池初始化函数,是不是还应该有从连接池中取出一条连接的函数。

//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL *connection_pool::GetConnection()
{
	MYSQL *con = 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;
}

一行一行看,不行就百度或者chat呗,先看前三行,其实不难理解,我先初始化一个空的mysql连接,然后检查连接池的大小是否为空,不能连接池里没可用的mysql连接了,我要还继续干下面的事,没有可用连接我就直接返回。

    reserve.wait();

这个reserve是什么呢,我们直接转到声明,可以在connection_pool.h看到

class connection_pool
{
private:
	locker lock; //互斥锁

	sem reserve; //信号量
}

 reserve其实就是信号量,wait函数如下

class sem
{
public:
    //等待信号量 m_sem 变为可用
    bool wait()
    {
        return sem_wait(&m_sem) == 0;
    }
}

直接看函数原型:

/* Wait for SEM being posted.
   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int sem_wait (sem_t *__sem) __nonnull ((1));

__nonnull ((x)) 的意思是从0开始的第x个参数不能为空,这是一个宏函数

上面的英语的意思就是必须要等信号量可用才能返回,连接池为空的时候信号量是不是同时就是0,也就是说信号量这时候不可用。

 接下来就是

	lock.lock();

lock相当于一把锁,这个线程池既然是全局唯一的,那么这个锁自然也是全局唯一的,因为这个锁是这个对象的一个变量,现在把线程池锁起来

    con = connList.front();
	connList.pop_front();

	--m_FreeConn;    //可用连接-1
	++m_CurConn;    //当前连接+1

	lock.unlock();
    return con;

让一开始初始化的mysql连接对象con得到连接池这个链表的第一条连接,然后解锁,返回一气呵成。

能从连接池得到一条连接是不是也需要释放一条连接

//释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
	if (NULL == con)
		return false;

	lock.lock();

	connList.push_back(con);//把一条连接添加到连接池的尾部
	++m_FreeConn;
	--m_CurConn;

	lock.unlock();

	reserve.post();
	return true;
}

需要说的其实只有下面一行:

    reserve.post();

它所做的事情其实就是信号量+1,下一个要说函数是用来销毁连接池的函数

//销毁数据库连接池
void connection_pool::DestroyPool()
{

	lock.lock();
	if (connList.size() > 0)
	{
		list::iterator it;
		for (it = connList.begin(); it != connList.end(); ++it)
		{
			MYSQL *con = *it;
			mysql_close(con);
		}
		m_CurConn = 0;
		m_FreeConn = 0;
		connList.clear();
	}

	lock.unlock();
}

其实就是加锁,迭代器循环然后释放每一条连接,最终再解锁,清空连接池就OK了,是不是很简单。下面两个函数就不说了,比较简单。

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

connection_pool::~connection_pool()
{
	DestroyPool();
}

以上就是connection_pool的全部函数了,这个文件还有另一个类 connectionRAII


class connectionRAII{
public:
	connectionRAII(MYSQL **SQL, connection_pool *connPool)
    {
	*SQL = connPool->GetConnection();
	
	conRAII = *SQL;
	poolRAII = connPool;
    }

	~connectionRAII(){
	    poolRAII->ReleaseConnection(conRAII)
    }
private:
	//持有的 MySQL 连接指针
	MYSQL *conRAII;

	// 指向连接池的指针,用于使用连接池函数
	connection_pool *poolRAII;
};

资源获取即初始化(Resource Acquisition Is Initialization,RAII)技术来管理 MySQL 数据库连接.
当 connectionRAII 对象被创建时,它会从连接池中获取一个连接,并将该连接的指针传递给外部指针 con。
当 connectionRAII 对象被销毁时,它会自动释放连接资源,避免了连接泄漏的问题。
 

 连接池也就说的差不多了,我对于具体容器和它的操作其实说的很少,这个不会的话自己搜一下就可以了

你可能感兴趣的:(数据库,oracle)