【前言】
现代服务器后台数据基本上都用数据库管理,因为他有一套完整的数据保存方案,本连接池是根据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】
点击打开链接