老规矩,先备份。
一、数据库连接池的介绍
其实就是定义多个数据库连接对象,这里可以用数组,也就是定义一个数据库连接对象数组,然后让每个数据库连接对象都去连接数据库。他们连接好数据库之后,我们又叫他们为数据库连接池。
当我们要使用数据库时,就从数据库连接池中取出一个对象来使用,通过这个已经连接好数据库的对象来操作数据库。那么我们就不用自己去定义一个数据库连接对象,然后再去等待连接数据库。这样就省下了自己去连接数据库的时间。
这个像是学校的水房,当我们要打热水了,不是自己去接一个水龙头到学校的锅炉中;而是使用学校已经安装好的水龙头,我们直接使用就可以打到热水了。我们就省下了每次要打热水就要自己接一个水龙头的时间。数据库连接池也是这原理,让数据库连接对象先连接好数据库,这就是先安装好水龙头;然后从数据库连接池中取出一个空闲对象使用,这就像是直接去寻找使用一个空闲的水龙头。因为有时候不只是你要使水龙头,或者不止一个线程要使用数据库;而是多个人,多个线程要使用。
所以你要检查一下那些水龙头是已经被使用了,这里我们可以理解为被人使用了,就相当于被人上锁了。在线程中也是,要检查哪些数据库连接对象是被上锁的,哪些是空闲的。有多少个水龙头就需要多少把锁,有多少个数据库连接对象就有多少把锁,这样有人使用的时候,就可以用这些锁来上锁了。
这里用互斥锁
二、数据库连接池的实现
1.定义数据连接对象数组和锁
这一步其实就是定义数据库连接池的大小。也就是要在水房中装多少个水龙头。有多少个数据库连接池就有多少把锁
2.初始化数据库连接池
这个步骤就是让数据库连接对象去连接数据库,还有初始化锁就是把锁做出来。这里最好是写一个日志
有多个数据库连接对象,多把锁,也已经知道数目,用一个for来初始化。连接成功一个数据库,就创建一个把锁。
3.将初始化锁放在客户端连接上来前
因为客户端连接到了服务端,就是要使用数据库连接对象和锁。那么我们要在客户端连接上来之前将数据库连接对象准备好(初始化)。
当创建数据库连接池失败时,要将已经连接的socket关闭,所以要调用EXIT()
三、数据库连接池的使用
创建好了数据库连接池,那么接下来就是要使用数据库连接池了。
1.函数去掉数据库连接对象参数
在之前没有采用数据库连接池时,是在线程的主函数中连接数据库,然后将数据库连接对象传给各个函数,现在需要将函数的这个参数去掉,因为我们不用传参数了,直接使用数据库连接池中的对象。
2.从数据库连接池中获取一个连接对象
当需要连接数据库时,我们就从数据库连接池中获取一个数据库连接对象,那要怎么获取呢?需要定义一个函数,去从连接池中遍历,如果遇到一个空闲的对象,就返回那个对象。
(1)定义获取一个连接对象函数 getconns(connection *conn)
下面步骤的代码改为这样:
(1.1)如何判断某个对象是空闲的
获取数据库连接池的方法就是,去遍历连接池,也就是遍历数组,看哪个连接对象没有被上锁。那没有被上锁的状态是什么样的呢?
其实要看某个对象有没有被上锁,可能我们是无法看出来,就像去上厕所一样。可能表面看不出来某个坑位是否被使用了,那么就去推一下门,如果能推进去说明我们就可以用了。
那么看连接池中的连接对象是否被上锁了或者说是否是空闲的,也是一样的道理。我们去给那个对象加锁,如果加锁成功了就说明这个对象没有被使用,是空闲的;如果加锁失败了,就是正在被使用的。
(1.2)如果连接池中没有空闲的对象了怎么处理
在上面的步骤中,如果没有空闲的连接对象,就直接return false 了。正确的做法是,没有空闲的了,那么我就等一段时间再去看看有没有空闲的,如果超过规定的次数了,还是没有那么我就走了。就是轮询
等待一段时间,我们利用sleep,再加上一个循环来实现。
(2)使用数据库连接对象
下面的步骤将要操作数据库,所以我们定义一个数据库连接对象的指针,并且传给获取对象的函数。这样就能获得一个数据库连接对象。
3.解锁
(1)解锁的思路
当线程使用完了数据库,不需要使用了,那么就将数据库连接对象解锁(不是摧毁),就像上厕所一样,上完了之后要将门解锁,走出去给下一个人使用。数据库连接对象解锁了,给另外的需要的线程使用。
解锁的思路就是在连接池中找到线程正在使用的对象,然后解锁。
(2)线程中解锁的位置
(2.1)操作数据库失败后
当线程操作数据库失败,比如主键冲突,会返回;此时也要注意解锁
(2.2)线程使用完了数据库,不再对数据库进行操作了
比如说一个业务要进行三次数据库操作,不能操作了一次就解锁了。
4.释放数据库连接对象和摧毁锁
在线程退出时,要将数据库连接对象释放和摧毁锁;我们将释放数据库连接对象和锁的事情放在线程退出时调用的函数 EXIT 中。我们自定义一个释放函数
四、注意事项
1.要采用非阻塞加锁
给数据库连接对象加锁的时候,要采用非阻塞的方式,不用阻塞的方式。我们正常的流程是,先看看第一个有没有锁,如果有就去看第二个,不是一直在那里等,这个就是非阻塞加锁。如果是阻塞加锁,如果看的那个对象被加锁了,我们就一直在那里等待它解锁,不去看其他的。
2.解锁的位置
(1)有return的地方就释放
当业务函数中有return 的地方都要解锁,不然连接对象一下子就耗光了。
(2)每次操作数据库才获取连接,操作完一次就释放
五、源码
#include "_freecplus.h"
#include "_ooci.h"
#define MAXCONNS 10 // 数据库连接池的大小。
pthread_mutex_t mutex[MAXCONNS]; // 锁数组。
connection conns[MAXCONNS]; // 数据库连接数组。
bool initconns(); // 初始数据库连接池。
connection *getconns(); // 从连接池中获取一个数据库连接。
bool freeconns(connection *in_conn); // 释放数据库连接。
// 业务请求
struct st_biz
{
int bizid; // 业务代码
char userid[51]; // 设备ID
int ttytype; // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
int usertype; // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
double lon;
double lat;
double height;
char obtid[11];
char xmlbuffer[1001];
};
// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz);
CTcpServer TcpServer;
CLogFile logfile;
// 程序退出时调用的函数
void EXIT(int sig);
// 与客户端通信线程的主函数
void *pth_main(void *arg);
// 心跳业务
bool biz10000(int clientfd);
// 新用户登录业务
bool biz10001(struct st_biz *stbiz,int clientfd);
// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd);
// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn);
// 存放客户端已连接的socket的容器
vector<int> vclientfd;
void AddClient(int clientfd); // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd); // 关闭客户端的socket并从vclientfd容器中删除,
// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd);
int main(int argc,char *argv[])
{
if (argc != 3)
{
printf("\n");
printf("Using:/htidc/shtqapp1/bin/shtqappserver1 logfilename port\n");
printf("Example:/htidc/shtqapp1/bin/shtqappserver1 /log/shtqapp/shtqappserver1.log 5015\n\n");
printf("本程序是上海天气APP软件的服务端。\n");
printf("logfilename 日志文件名。\n");
printf("port 用于传输文件的TCP端口。\n");
return -1;
}
// 关闭全部的信号和输入输出
// 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
// 但请不要用 "kill -9 +进程号" 强行终止
CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);
// 打开程序运行日志,这是一个多进程程序,日志不能自动切换
if (logfile.Open(argv[1],"a+",false) == false)
{
printf("logfile.Open(%s) failed.\n",argv[1]); return -1;
}
logfile.Write("shtqappserver started(%s).\n",argv[2]);
if (TcpServer.InitServer(atoi(argv[2])) == false)
{
logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[2]); EXIT(-1);
}
// 保存服务端的listenfd到vclientfd
AddClient(TcpServer.m_listenfd);
if (initconns()==false) // 初始化数据库连接池。
{
logfile.Write("initconns() failed.\n"); EXIT(-1);
}
while (true)
{
// 等待客户端的连接
if (TcpServer.Accept() == false)
{
logfile.Write("TcpServer.Accept() failed.\n"); continue;
}
pthread_t pthid; // 创建一线程,与新连接上来的客户端通信
if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
{
logfile.Write("创建线程失败,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
}
logfile.Write("%s is connected.\n",TcpServer.GetIP());
// 保存每个客户端的socket到vclientfd
AddClient(TcpServer.m_connfd);
}
return 0;
}
// 退出时调用的函数
void EXIT(int sig)
{
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
if (sig>0) signal(sig,SIG_IGN);
logfile.Write("tcpfileserver1 exit,sig=%d...\n",sig);
// 关闭vclientfd容器中全部的socket
for (int ii=0;ii<vclientfd.size();ii++)
{
close(vclientfd[ii]);
}
for (int ii=0;ii<MAXCONNS;ii++)
{
logfile.Write("disconnect and pthread_mutex_destroy.\n");
conns[ii].disconnect();
pthread_mutex_destroy(&mutex[ii]);
}
exit(0);
}
// 与客户端通信线程的主函数
void *pth_main(void *arg)
{
int clientfd=(long) arg; // arg参数为新客户端的socket。
pthread_detach(pthread_self());
struct st_biz stbiz;
int ibuflen=0;
char strRecvBuffer[1024]; // 接收报文的缓冲区
while (true)
{
memset(strRecvBuffer,0,sizeof(strRecvBuffer));
// 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,不写错误日志
if (TcpRead(clientfd,strRecvBuffer,&ibuflen,50) == false)
{
// logfile.Write("TcpRead() failed.\n");
break;
}
logfile.Write("strRecvBuffer=%s\n",strRecvBuffer); // xxxxxx
// 把参数解析出来
xmltobiz(strRecvBuffer,&stbiz);
if (stbiz.bizid==10000) // 心跳报文
{
if (biz10000(clientfd)==true) continue;
else break;
}
// 新用户登录
if (stbiz.bizid==10001)
{
connection *conn = getconn();
if (biz10001(&stbiz,clientfd,conn)==true)
freeconn(conn);
continue;
else break;
}
// 获取天气实况
if (stbiz.bizid==10002)
{
if (biz10002(&stbiz,clientfd)==true) continue;
else break;
}
// 体力活
logfile.Write("非法报文%s\n",strRecvBuffer); break;
}
RemoveClient(clientfd);
pthread_exit(0);
}
// 把xml解析到参数starg结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz)
{
memset(stbiz,0,sizeof(struct st_biz));
// 业务代码
GetXMLBuffer(strxmlbuffer,"bizid",&stbiz->bizid);
// logfile.Write("bizid=%d\n",stbiz->bizid);
// 用户设备ID
GetXMLBuffer(strxmlbuffer,"userid",stbiz->userid,50);
// logfile.Write("userid=%s\n",stbiz->userid);
GetXMLBuffer(strxmlbuffer,"obtid",stbiz->obtid,10);
// logfile.Write("obtid=%s\n",stbiz->obtid);
GetXMLBuffer(strxmlbuffer,"lat",&stbiz->lat);
// logfile.Write("lat=%lf\n",stbiz->lat);
GetXMLBuffer(strxmlbuffer,"lon",&stbiz->lon);
// logfile.Write("lon=%lf\n",stbiz->lon);
GetXMLBuffer(strxmlbuffer,"height",&stbiz->height);
// logfile.Write("height=%lf\n",stbiz->height);
strncpy(stbiz->xmlbuffer,strxmlbuffer,1000);
return;
}
// 心跳业务
bool biz10000(int clientfd)
{
char strSendBuffer[1024]; // 发送报文的缓冲区
memset(strSendBuffer,0,sizeof(strSendBuffer));
strcpy(strSendBuffer,"ok");
if (TcpWrite(clientfd,strSendBuffer) == false)
{
logfile.Write("biz10000 TcpWrite() failed.\n"); return false;
}
return true;
}
// 新用户登录
bool biz10001(struct st_biz *stbiz,int clientfd)
{
CTimer Timer;
char strSendBuffer[1024]; // 发送报文的缓冲区
connection *conn=getconns(); // 获取一个数据库连接。
// 插入用户基本信息表T_USERINFO
sqlstatement stmt(conn);
stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
stmt.bindin(1, stbiz->userid,50);
stmt.bindin(2,&stbiz->ttytype);
if (stmt.execute() != 0)
{
if (stmt.m_cda.rc!=1)
{
logfile.Write("insert T_USERINFO failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
}
}
logfile.Write("insert T_USERINFO =%lf\n",Timer.Elapsed());
// 插入用户请求日志表
if (InsertUSERLOG(stbiz,conn)==false) { freeconns(conn); return false; }
logfile.Write("insert T_USERLOG =%lf\n",Timer.Elapsed());
char strobtid[6],strobtname[31],strlon[11],strlat[11];
stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
stmt.bindout(1,strobtid,5);
stmt.bindout(2,strobtname,30);
stmt.bindout(3,strlon,10);
stmt.bindout(4,strlat,10);
if (stmt.execute() != 0)
{
logfile.Write("select T_OBTCODE failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
}
while (true)
{
memset(strobtid,0,sizeof(strobtid));
memset(strobtname,0,sizeof(strobtname));
memset(strlon,0,sizeof(strlon));
memset(strlat,0,sizeof(strlat));
memset(strSendBuffer,0,sizeof(strSendBuffer));
if (stmt.next()!=0) break;
sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);
if (TcpWrite(clientfd,strSendBuffer) == false)
{
logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
}
}
logfile.Write("select =%lf\n",Timer.Elapsed());
// 最后发送一个ok
strcpy(strSendBuffer,"ok");
if (TcpWrite(clientfd,strSendBuffer) == false)
{
logfile.Write("biz10001 TcpWrite() failed.\n"); freeconns(conn); return false;
}
freeconns(conn);
return true;
}
// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn)
{
sqlstatement stmt(conn);
stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
stmt.bindin(1, stbiz->userid,50);
stmt.bindin(2,&stbiz->bizid);
stmt.bindin(3, stbiz->obtid,10);
stmt.bindin(4,&stbiz->lon);
stmt.bindin(5,&stbiz->lat);
stmt.bindin(6,&stbiz->height);
stmt.bindin(7, stbiz->xmlbuffer,10000);
if (stmt.execute() != 0)
{
logfile.Write("insert T_USERLOG failed.\n%s\n%s\n",stmt.m_cda.message,stmt.m_sql); return false;
}
return true;
}
// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd)
{
return true;
}
// 把客户端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
vclientfd.push_back(clientfd);
}
// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd)
{
for (int ii=0;ii<vclientfd.size();ii++)
{
if (vclientfd[ii]==clientfd)
{ close(clientfd); vclientfd.erase(vclientfd.begin()+ii); return; }
}
}
bool initconns() // 初始数据库连接池
{
for (int ii=0;ii<MAXCONNS;ii++)
{
logfile.Write("%d,connecttodb and pthread_mutex_init.\n",ii);
// 连接数据库
if (conns[ii].connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
{
logfile.Write("conns[%d].connettodb() failed.\n",ii); return false;
}
pthread_mutex_init(&mutex[ii],0); // 创建锁
}
return true;
}
connection *getconns()
{
// for (int jj=0;jj<1000;jj++)
while (true)
{
for (int ii=0;ii<MAXCONNS;ii++)
{
if (pthread_mutex_trylock(&mutex[ii])==0)
{
// logfile.Write("jj=%d,ii=%d\n",jj,ii);
logfile.Write("get conns is %d.\n",ii);
return &conns[ii];
}
}
usleep(10000);
}
}
bool freeconns(connection *in_conn)
{
for (int ii=0;ii<MAXCONNS;ii++)
{
if (in_conn==&conns[ii])
{
logfile.Write("free conn %d.\n",ii);
pthread_mutex_unlock(&mutex[ii]); in_conn=0; return true;
}
}
return false;
}