【前言】
现代服务器后台数据基本上都用数据库管理,因为他有一套完整的数据保存方案,本连接池是根据MYSQL提供的纯C API封装。
【上回战况】
目前,服务器添加了文件服务器类,只是简单的文件传输;但是没有使用数据库保存文件的信息,随着数据的增加,文件的管理肯定就很难。
【目前战况】
为了同意所有线程访问数据库的方式,封装一个连接池,如下:
class MYSQLConnPool
{
public:
typedef struct
{
bool flag; /* 判断此连接是否已经被占用(true) */
MYSQL *conn;
}SMYSQL;
public:
/* Singleton模式 */
static MYSQLConnPool* CreateConnPool(const char *host, const char *user, const char *psw, const char *db, int conn_num)
{
if (conn_num > MAX_CONN_NUM)
return nullptr;
if (m_ConnPool == nullptr)
{
m_ConnPool = new MYSQLConnPool(host, user, psw, db, conn_num);
if (m_ConnPool)
return m_ConnPool;
return nullptr;
}
return nullptr;
}
/* 成功返回该调用线程所使用连接的索引,出错返回mysql_query对应的返回值 */
int Query(const char *sql);
/* 根据Query返回值查询指定索引连接的返回结果 */
MYSQL_RES *StoreResult(int idx);
/* 释放结果后才能释放连接(使连接处于空闲可被调用) */
void FreeResult(MYSQL_RES *res, int idx);
~MYSQLConnPool()
{
for (int i = 0; i < m_ConnNum; ++i)
{
mysql_close(m_ConnVec[i].conn);
}
}
private:
MYSQLConnPool(const char *host, const char *user, const char *psw, const char *db, int conn_num);
private:
/* 连接池最大连接数 */
static const int MAX_CONN_NUM = 16;
/* 连接池的连接数 */
int m_ConnNum;
pthread_mutex_t m_ListMutex;
sem_t m_ListSem;
/* 连接列表 */
std::vector<SMYSQL> m_ConnVec;
static MYSQLConnPool *m_ConnPool;
};
MYSQLConnPool* MYSQLConnPool::m_ConnPool = nullptr;
MYSQLConnPool::MYSQLConnPool(const char *host, const char *user, const char *psw, const char *db, int conn_num)
{
/* 根据连接数量初始化信号量 */
int ret = sem_init(&m_ListSem, 0, conn_num);
m_ListMutex = PTHREAD_MUTEX_INITIALIZER;
m_ConnNum = conn_num;
// 建立数据库连接
for (int i = 0; i < conn_num; ++i)
{
MYSQL *conn = mysql_init(nullptr);
if (!conn)
continue;
conn = mysql_real_connect(conn, host, user, psw, db, 0, nullptr, 0);
if (!conn)
continue;
m_ConnVec.push_back({false, conn});
}
}
int MYSQLConnPool::Query(const char *sql)
{
sem_wait(&m_ListSem);
/* 有空闲连接,设置flag,相当于上锁 */
int idx = 0;
pthread_mutex_lock(&m_ListMutex);
for (idx = 0; idx < m_ConnNum; ++idx)
{
if (!m_ConnVec[idx].flag)
{
m_ConnVec[idx].flag = true;
break;
}
}
pthread_mutex_unlock(&m_ListMutex);
/* 执行查询 */
int ret = mysql_query(m_ConnVec[idx].conn, sql);
return ret == 0?idx:ret; // 如果uqery成功,直接返回该连接的索引,供store/freestore调用。
}
MYSQL_RES *MYSQLConnPool::StoreResult(int idx)
{
return mysql_store_result(m_ConnVec[idx].conn);
}
void MYSQLConnPool::FreeResult(MYSQL_RES *res, int idx)
{
mysql_free_result(res);
m_ConnVec[idx].flag = false;
sem_post(&m_ListSem);
}
这里使用了singleton模式,每个子进程只允许创建一个连接池。使用vector保存所有连接,同时通过互斥量和信号量防止竞争。
我的想法:既然我们的目的是封装一个连接池,那么我们关心的更多是连接池的管理,关于MYSQL数据库的访问应该尽可能脱离连接池的封装。所以,这里我只将查询/返回结果/释放结果进行封装,这样做我还有另一个原因:因为如果直接将连接变量(MYSQL*)返回,然后在客户程序是直接使用MYSQL提供的纯C API,那么就破坏了封装性。另外,需要注意的是:对于每个连接变量被用于query调用时,在下次再query前,必须调用store/freestore来释放结果。所以,我更坚定的封装这三个函数。
【Github】